您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)C#多線程中線程同步的示例分析,小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。
我們先來(lái)看下面一個(gè)例子:
using System; using System.Threading; namespace ThreadSynchDemo { class Program { private static int Counter = 0; static void Main(string[] args) { Thread t1 = new Thread(() => { for (int i = 0; i < 1000; i++) { Counter++; Thread.Sleep(1); } }); t1.Start(); Thread t2 = new Thread(() => { for (int i = 0; i < 1000; i++) { Counter++; Thread.Sleep(1); } }); t2.Start(); Thread.Sleep(3000); Console.WriteLine(Counter); Console.ReadKey(); } } }
我們猜想一下程序的輸出結(jié)果是多少?2000?我們運(yùn)行程序看一下輸出結(jié)果:
我們看到,程序最后輸出的結(jié)果跟我們預(yù)測(cè)的完全不一樣,這是什么原因呢?這就是由線程同步引起的問(wèn)題。
線程同步問(wèn)題:是解決多個(gè)線程同時(shí)操作一個(gè)資源的問(wèn)題
。
在上面的例子中,t1和t2兩個(gè)線程里面都是讓變量Counter的值自增1,假設(shè)這時(shí)t1線程讀取到Counter的值為200,可能t2線程執(zhí)行非???,t1線程讀取Counter值的時(shí)候,t2線程已經(jīng)把Counter的值改為了205,等t1線程執(zhí)行完畢以后,Counter的值又被變?yōu)榱?01,這樣就會(huì)出現(xiàn)線程同步的問(wèn)題了。那么該如何解決這個(gè)問(wèn)題呢?
解決線程同步問(wèn)題最簡(jiǎn)單的是使用lock。lock可以解決多個(gè)線程同時(shí)操作一個(gè)資源引起的問(wèn)題。lock是C#中的關(guān)鍵字,它要鎖定一個(gè)資源,lock的特點(diǎn)是:同一時(shí)刻只能有一個(gè)線程進(jìn)入lock的對(duì)象的范圍,其它lock的線程都要等待。我們看下面優(yōu)化后的代碼:
using System; using System.Threading; namespace ThreadSynchDemo { class Program { private static int Counter = 0; // 定義一個(gè)locker對(duì)象 private static Object locker = new Object(); static void Main(string[] args) { #region 存在線程同步問(wèn)題 //Thread t1 = new Thread(() => { // for (int i = 0; i < 1000; i++) // { // Counter++; // Thread.Sleep(1); // } //}); //t1.Start(); //Thread t2 = new Thread(() => { // for (int i = 0; i < 1000; i++) // { // Counter++; // Thread.Sleep(1); // } //}); //t2.Start(); #endregion #region 使用Lock解決線程同步問(wèn)題 Thread t1 = new Thread(() => { for (int i = 0; i < 1000; i++) { lock(locker) { Counter++; } Thread.Sleep(1); } }); t1.Start(); Thread t2 = new Thread(() => { for (int i = 0; i < 1000; i++) { lock (locker) { Counter++; } Thread.Sleep(1); } }); t2.Start(); #endregion Thread.Sleep(3000); Console.WriteLine(Counter); Console.ReadKey(); } } }
這時(shí)我們?cè)谶\(yùn)行程序,查看輸出結(jié)果:
這時(shí)輸出結(jié)果是正確的。
注意:lock只能鎖住同一個(gè)對(duì)象,如果是不同的對(duì)象,還是會(huì)有線程同步的問(wèn)題。lock鎖定的對(duì)象必須是引用類型的對(duì)象。
我們?cè)诙x一個(gè)Object類型的對(duì)象,lock分別鎖住兩個(gè)對(duì)象,看看是什么結(jié)果:
using System; using System.Threading; namespace ThreadSynchDemo { class Program { private static int Counter = 0; // 定義一個(gè)locker對(duì)象 private static Object locker = new Object(); // 定義locker2 private static Object locker2 = new Object(); static void Main(string[] args) { #region 存在線程同步問(wèn)題 //Thread t1 = new Thread(() => { // for (int i = 0; i < 1000; i++) // { // Counter++; // Thread.Sleep(1); // } //}); //t1.Start(); //Thread t2 = new Thread(() => { // for (int i = 0; i < 1000; i++) // { // Counter++; // Thread.Sleep(1); // } //}); //t2.Start(); #endregion #region 使用Lock解決線程同步問(wèn)題 //Thread t1 = new Thread(() => { // for (int i = 0; i < 1000; i++) // { // lock(locker) // { // Counter++; // } // Thread.Sleep(1); // } //}); //t1.Start(); //Thread t2 = new Thread(() => { // for (int i = 0; i < 1000; i++) // { // lock (locker) // { // Counter++; // } // Thread.Sleep(1); // } //}); //t2.Start(); #endregion #region 使用lock鎖住不同的對(duì)象也會(huì)有線程同步問(wèn)題 Thread t1 = new Thread(() => { for (int i = 0; i < 1000; i++) { lock (locker) { Counter++; } Thread.Sleep(1); } }); t1.Start(); Thread t2 = new Thread(() => { for (int i = 0; i < 1000; i++) { lock (locker2) { Counter++; } Thread.Sleep(1); } }); t2.Start(); #endregion Thread.Sleep(3000); Console.WriteLine(Counter); Console.ReadKey(); } } }
程序運(yùn)行結(jié)果:
可以看到,這時(shí)還是會(huì)有線程同步的問(wèn)題。雖然使用了lock,但是我們鎖住的是不同的對(duì)象,這樣也會(huì)有線程同步問(wèn)題。lock必須鎖住同一個(gè)對(duì)象才可以。
我們下面在來(lái)看一個(gè)多線程同步問(wèn)題的例子:
using System; using System.Threading; namespace ThreadSynchDemo2 { class Program { static int Money = 100; /// <summary> /// 定義一個(gè)取錢的方法 /// </summary> /// <param name="name"></param> static void QuQian(string name) { Console.WriteLine(name + "查看一下余額" + Money); int yue = Money - 1; Console.WriteLine(name + "取錢"); Money = yue; Console.WriteLine(name + "取完了,剩" + Money); } static void Main(string[] args) { Thread t1 = new Thread(() => { for (int i = 0; i < 10; i++) { QuQian("t2"); } }); Thread t2 = new Thread(() => { for (int i = 0; i < 10; i++) { QuQian("t2"); } }); t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.WriteLine("余額" + Money); Console.ReadKey(); } } }
我們看一下輸出結(jié)果:
可以看到,最終的余額并不是80,這也是線程同步帶來(lái)的問(wèn)題,如何解決。解決思路就是使用同步的技術(shù)避免兩個(gè)線程同時(shí)修改一個(gè)余額。
在方法上面使用[MethodImpl(MethodImplOptions.Synchronized)],標(biāo)記該方法是同步方法,這樣一個(gè)方法只能同時(shí)被一個(gè)線程訪問(wèn)。我們?cè)赒uQian的方法上面標(biāo)記,修改后的代碼如下:
using System; using System.Runtime.CompilerServices; using System.Threading; namespace ThreadSynchDemo2 { class Program { static int Money = 100; /// <summary> /// 定義一個(gè)取錢的方法,在上面標(biāo)記為同步方法 /// </summary> /// <param name="name"></param> [MethodImpl(MethodImplOptions.Synchronized)] static void QuQian(string name) { Console.WriteLine(name + "查看一下余額" + Money); int yue = Money - 1; Console.WriteLine(name + "取錢"); Money = yue; Console.WriteLine(name + "取完了,剩" + Money); } static void Main(string[] args) { Thread t1 = new Thread(() => { for (int i = 0; i < 10; i++) { QuQian("t2"); } }); Thread t2 = new Thread(() => { for (int i = 0; i < 10; i++) { QuQian("t2"); } }); t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.WriteLine("余額" + Money); Console.ReadKey(); } } }
程序輸出結(jié)果:
現(xiàn)在的方法就是“線程安全”的了。什么是“線程安全”呢?“線程安全”是指方法可以被多個(gè)線程隨意調(diào)用,而不會(huì)出現(xiàn)混亂。如果出現(xiàn)了混亂,那么就是“線程不安全”的?!熬€程安全”的方法可以在多線程里面隨意的使用。
對(duì)象互斥鎖就是我們上面講的lock。我們?cè)谟胠ock來(lái)修改上面QuQian的例子:
using System; using System.Runtime.CompilerServices; using System.Threading; namespace ThreadSynchDemo2 { class Program { static int Money = 100; /// <summary> /// 定義一個(gè)取錢的方法,在上面標(biāo)記為同步方法 /// </summary> /// <param name="name"></param> //[MethodImpl(MethodImplOptions.Synchronized)] //static void QuQian(string name) //{ // Console.WriteLine(name + "查看一下余額" + Money); // int yue = Money - 1; // Console.WriteLine(name + "取錢"); // Money = yue; // Console.WriteLine(name + "取完了,剩" + Money); //} private static object locker = new object(); static void QuQian(string name) { Console.WriteLine(name + "查看一下余額" + Money); int yue = Money - 1; Console.WriteLine(name + "取錢"); Money = yue; Console.WriteLine(name + "取完了,剩" + Money); } static void Main(string[] args) { Thread t1 = new Thread(() => { for (int i = 0; i < 10; i++) { // 使用對(duì)象互斥鎖 lock(locker) { QuQian("t1"); } } }); Thread t2 = new Thread(() => { for (int i = 0; i < 10; i++) { lock (locker) { QuQian("t2"); } } }); t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.WriteLine("余額" + Money); Console.ReadKey(); } } }
程序輸出結(jié)果:
可以看到,最終的輸出結(jié)果還是80。
同一時(shí)刻只能有一個(gè)線程進(jìn)入同一個(gè)對(duì)象的lock代碼塊。必須是同一個(gè)對(duì)象才能起到互斥的作用。lock后必須是引用類型,不一定是object,只要是對(duì)象就行。
鎖對(duì)象選擇很重要,選不對(duì)就起不到同步的作用;選不對(duì)還有可能會(huì)造成其他地方被鎖,比如用字符串做鎖(因?yàn)樽址彌_池導(dǎo)致導(dǎo)致可能用的是其他地方正在使用的鎖),所以不建議使用字符串做鎖。下面的代碼就是不允許的:
lock("locker")
兩個(gè)方法如果都用一個(gè)對(duì)象做鎖,那么訪問(wèn)A的時(shí)候就不能訪問(wèn)B,因此鎖選擇很重要。
其實(shí)lock關(guān)鍵字就是對(duì)Monitor的簡(jiǎn)化調(diào)用,lock最終會(huì)被編譯成Monitor,因此一般不直接使用Monitor類,看下面代碼:
using System; using System.Threading; namespace MonitorDemo { class Program { static int Money = 100; private static object locker = new object(); static void QuQian(string name) { // 等待沒(méi)有人鎖定locker對(duì)象,就鎖定它,然后繼續(xù)執(zhí)行 Monitor.Enter(locker); try { Console.WriteLine(name + "查看一下余額" + Money); int yue = Money - 1; Console.WriteLine(name + "取錢"); Money = yue; Console.WriteLine(name + "取完了,剩" + Money); } finally { // 釋放locker對(duì)象的鎖 Monitor.Exit(locker); } } static void Main(string[] args) { Thread t1 = new Thread(() => { for (int i = 0; i < 10; i++) { QuQian("t1"); } }); Thread t2 = new Thread(() => { for (int i = 0; i < 10; i++) { QuQian("t2"); } }); t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.WriteLine("余額" + Money); Console.ReadKey(); } } }
程序輸出結(jié)果:
Monitor類里面還有TryEnter方法,如果Enter的時(shí)候有人在占用鎖,它不會(huì)等待,而是會(huì)返回false??聪旅娴氖纠a:
using System; using System.Threading; namespace MonitorDemo { class Program { static int Money = 100; private static object locker = new object(); static void QuQian(string name) { // 等待沒(méi)有人鎖定locker對(duì)象,就鎖定它,然后繼續(xù)執(zhí)行 Monitor.Enter(locker); try { Console.WriteLine(name + "查看一下余額" + Money); int yue = Money - 1; Console.WriteLine(name + "取錢"); Money = yue; Console.WriteLine(name + "取完了,剩" + Money); } finally { // 釋放locker對(duì)象的鎖 Monitor.Exit(locker); } } static void F1(int i) { if (!Monitor.TryEnter(locker)) { Console.WriteLine("有人在鎖著呢"); return; } Console.WriteLine(i); Monitor.Exit(locker); } static void Main(string[] args) { //Thread t1 = new Thread(() => { // for (int i = 0; i < 10; i++) // { // QuQian("t1"); // } //}); //Thread t2 = new Thread(() => { // for (int i = 0; i < 10; i++) // { // QuQian("t2"); // } //}); Thread t1 = new Thread(() => { for (int i = 0; i < 10; i++) { F1(i); } }); Thread t2 = new Thread(() => { for (int i = 0; i < 10; i++) { F1(i); } }); t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.WriteLine("余額" + Money); Console.ReadKey(); } } }
程序輸出結(jié)果:
關(guān)于“C#多線程中線程同步的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。