线程理解(三)
?
- 線程同步中的一些重要概念
- ??? 臨界區(qū)(共享區(qū))的概念
- ????基元用戶模式
- ??? 基元內(nèi)核模式
- ????原子性操作
- ????非阻止同步
- ????阻止同步
- 詳解Thread類 中的VolatileRead和VolatileWrite方法和Volatile關(guān)鍵字的作用
- ???? Volatile關(guān)鍵字的作用
- 介紹下Interlocked
- 介紹下Lock關(guān)鍵字
- 詳解ReaderWriterLock 類
- 本章總結(jié)
- 參考文獻
?
?
1.線程同步中的一些重要概念
?
1.1臨界區(qū)(共享區(qū))的概念
? ???? 在多線程的環(huán)境中,可能需要共同使用一些公共資源,這些資源可能是變量,方法邏輯段等等,這些被多個線程
共用的區(qū)域統(tǒng)稱為臨界區(qū)(共享區(qū)),聰明的你肯定會想到,臨界區(qū)的資源不是很安全,因為線程的狀態(tài)是不定的,所以
可能帶來的結(jié)果是臨界區(qū)的資源遭到其他線程的破壞,我們必須采取策略或者措施讓共享區(qū)數(shù)據(jù)在多線程的環(huán)境下保持
完成性不讓其受到多線程訪問的破壞
?
1.2基元用戶模式
? ???? 可能大家覺得這個很難理解,的確如果光看概念解釋的話,會讓人抓狂的,因為這個模式牽涉到了深奧的底層cup
內(nèi)核和windows的一些底層機制,所以我用最簡單的理解相信大家一定能理解,因為這對于理解同步也很重要
回到正題,基元用戶模式是指使用cpu的特殊指令來調(diào)度線程,所以這種協(xié)調(diào)調(diào)度線程是在硬件中進行的所以得出
了它第一些優(yōu)點:
?????? 速度特別快
???????線程阻塞時間特別短
但是由于該模式中的線程可能被系統(tǒng)搶占,導(dǎo)致該模式中的線程為了獲取某個資源,而浪費許多cpu時間,同時如果一直處
于等待的話會導(dǎo)致”活鎖”,也就是既浪費了內(nèi)存,又浪費了cpu時間,這比下文中的死鎖更可怕,那么如何利用強大的
cpu時間做更多的事呢?那就引出了下面的一個模式
?
1.3基元內(nèi)核模式
該模式和用戶模式不同,它是windows系統(tǒng)自身提供的,使用了操作系統(tǒng)中內(nèi)核函數(shù),所以它能夠阻塞線程提高了cpu的利
用率,同時也帶來了一個很可怕的bug,死鎖,可能線程會一直阻塞導(dǎo)致程序的奔潰,常用的內(nèi)核模式的技術(shù)例如Monitor,Mutex,
等等會在下一章節(jié)介紹。本章將詳細討論鎖的概念,使用方法和注意事項
?
*1.4原子性操作
如果一個語句執(zhí)行一個單獨不可分割的指令,那么它是原子的。嚴格的原子操作排除了任何搶占的可能性(這也是實現(xiàn)同步的一
個重要條件,也就是說沒有一個線程可以把這個美女占為己有,更方便的理解是這個值永遠是最新的),在c#中原子操作如下圖所示:
其實要符合原子操作必須滿足以下條件
相信大家能夠理解原子的特點,下文中的Volatil和interlocked會詳細模擬原子操作來實現(xiàn)線程同步,所以在使用原子操
作時也需要注意當(dāng)前操作系統(tǒng)是32位或是64位cpu或者兩者皆要考慮
?
1.5非阻止同步
非阻止同步說到底,就是利用原子性操作實現(xiàn)線程間的同步,不刻意阻塞線程,減少相應(yīng)線程的開銷,下文中的VolatileRead,V
olatileWrite,Volatile關(guān)鍵字,interlocked類便是c#中非阻止同步的理念所產(chǎn)生的線程同步技術(shù)
?
? 1.6阻止同步
????? ?阻止同步正好相反,其實阻止同步也是基元內(nèi)核模式的特點之一,例如c# 中的鎖機制,及其下幾章介紹的mutex,monitor等都屬
于阻止同步,他們的根本目的是,以互斥的效果讓同一時間只有一個線程能夠訪問共享區(qū),其他線程必須阻止等待,直到該線程離開共享
區(qū)后,在讓其他一個線程訪問共享區(qū),阻止同步缺點也是容易產(chǎn)生死鎖,但是阻止同步提高了cpu時間的利用率
?
2.詳解Thread類中的VolatileRead和VolatileWrite方法和Volatile關(guān)鍵字
???? 前文中,我們已經(jīng)對原子操作和非阻止同步的概念已經(jīng)有了大概的認識,接著讓我們從新回到Thread類來看下其中比較經(jīng)典的VolatileRead
和VolatileWrite方法
| VolatileWrite: ?? 該方法作用是,當(dāng)線程在共享區(qū)(臨界區(qū))傳遞信息時,通過此方法來原子性的寫入最后一個值 VolatileRead: ?? 該方法作用是,當(dāng)線程在共享區(qū)(臨界區(qū))傳遞信息時,通過此方法來原子性的讀取第一個值。 |
?
?
?
?
?
可能這樣的解釋會讓大家困惑,老規(guī)矩,直接上例子讓大家能夠理解:
/// <summary>/// 本例利用VolatileWrite和VolatileRead來實現(xiàn)同步,來實現(xiàn)一個計算/// 的例子,每個線程負責(zé)運算1000萬個數(shù)據(jù),共開啟10個線程計算至1億, /// 而且每個線程都無法干擾其他線程工作 /// </summary> class Program { static Int32 count;//計數(shù)值,用于線程同步 (注意原子性,所以本例中使用int32) static Int32 value;//實際運算值,用于顯示計算結(jié)果 static void Main(string[] args) { //開辟一個線程專門負責(zé)讀value的值,這樣就能看見一個計算的過程 Thread thread2 = new Thread(new ThreadStart(Read)); thread2.Start(); //開辟10個線程來負責(zé)計算,每個線程負責(zé)1000萬條數(shù)據(jù) for (int i = 0; i < 10; i++) { Thread.Sleep(20); Thread thread = new Thread(new ThreadStart(Write)); thread.Start(); } Console.ReadKey(); } /// <summary> /// 實際運算寫操作 /// </summary> private static void Write() { Int32 temp = 0; for (int i = 0; i < 10000000; i++) { temp += 1; } value += temp; //注意VolatileWrite 在每個線程計算完畢時會寫入同步計數(shù)值為1,告訴程序該線程已經(jīng)執(zhí)行完畢 //所以VolatileWrite方法類似與一個按鈴,往往在原子性的最后寫入告訴程序我完成了 Thread.VolatileWrite(ref count, 1); } /// <summary> /// 顯示計算后的數(shù)據(jù),使用該方法的線程會死循環(huán)等待寫 /// 操作的線程發(fā)出完畢信號后顯示當(dāng)前計算結(jié)果 /// </summary> private static void Read() { while (true) { //一旦監(jiān)聽到一個寫操作線執(zhí)行完畢后立刻顯示操作結(jié)果 //和VolatileWrite相反,VolatileRead類似一個門禁,只有原子性的最先讀取他,才能達到同步效果 //同時count值保持最新 if (Thread.VolatileRead(ref count) > 0) { Console.WriteLine("累計計數(shù):{1}", Thread.CurrentThread.ManagedThreadId, value); //將count設(shè)置成0,等待另一個線程執(zhí)行完畢 count = 0; } } } }顯示結(jié)果:
例子中我們可以看出當(dāng)個線程調(diào)用Read方法時,代碼會先判斷Thread. VolatileRead先讀取計數(shù)值是否返回正確的計數(shù)值,如果正確則顯示
結(jié)果,不正確的話繼續(xù)循環(huán)等待,而這個返回值是通過其他線程操作Write方法時最后寫入的,也就是說對于Thread. VolatileWrite
方法的作用便一目了然了,在實現(xiàn)Thread. VolatileWrite前寫入其他的數(shù)據(jù)或進行相應(yīng)的邏輯處理,在我們示例代碼中我們會先去加運算到
10000000時,通過thread. VolatileWrite原子性的操作寫入計數(shù)值告訴那個操作Read方法的線程有一個計算任務(wù)已經(jīng)完成,于是死循環(huán)中
的Thread. VolatileRead方法接受到了信號,你可以顯示計算結(jié)果了,于是結(jié)果便會被顯示,同時計數(shù)值歸零,這樣便起到了一個非阻塞功能
的同步效果,同樣對于臨界區(qū)(此例中的Write方法體和Read方法體)起到了保護的作用。當(dāng)然由于使用上述兩個方法在復(fù)雜的項目中很容易
出錯,往往這種錯誤是很難被發(fā)現(xiàn),所以微軟為了讓我們更好使用,便開發(fā)出了一個新的關(guān)鍵字Volatile:
Volatile關(guān)鍵字的作用
Volatile關(guān)鍵字的本質(zhì)含義是告訴編譯器,聲明為Volatile關(guān)鍵字的變量或字段都是提供給多個線程使用的,當(dāng)然不是每個類型都
可以聲明為Volatile類型字段,msdn中詳細說明了那些類型可以聲明為Volatile 所以不再陳述,但是有一點必須注意,Volatile
無法聲明為局部變量。作為原子性的操作,Volatile關(guān)鍵字具有原子特性,所以線程間無法對其占有,它的值永遠是最新的。那我
們就對上文的那個例子簡化如下:
View Code /// <summary>/// 本例利用volatile關(guān)鍵字來實現(xiàn)同步,來實現(xiàn)一個計算/// 的例子,每個線程負責(zé)運算1000萬個數(shù)據(jù),共開啟10個線程計算至1億, /// 而且每個線程都無法干擾其他線程工作 /// </summary> class Program { static volatile Int32 count;//計數(shù)值,用于線程同步 (注意原子性,所以本例中使用int32) static Int32 value;//實際運算值,用于顯示計算結(jié)果 static void Main(string[] args) { //開辟一個線程專門負責(zé)讀value的值,這樣就能看見一個計算的過程 Thread thread2 = new Thread(new ThreadStart(Read)); thread2.Start(); //開辟10個線程來負責(zé)計算,每個線程負責(zé)1000萬條數(shù)據(jù) for (int i = 0; i < 10; i++) { Thread.Sleep(20); Thread thread = new Thread(new ThreadStart(Write)); thread.Start(); } Console.ReadKey(); } /// <summary> /// 實際運算寫操作 /// </summary> private static void Write() { Int32 temp = 0; for (int i = 0; i < 10000000; i++) { temp += 1; } value += temp; //注意VolatileWrite 在每個線程計算完畢時會寫入同步計數(shù)值為1,告訴程序該線程已經(jīng)執(zhí)行完畢 //將count值設(shè)置成1,效果等同于Thread.VolatileWrite count = 1; } /// <summary> /// 顯示計算后的數(shù)據(jù),使用該方法的線程會死循環(huán)等待寫 /// 操作的線程發(fā)出完畢信號后顯示當(dāng)前計算結(jié)果 /// </summary> private static void Read() { while (true) { //一旦監(jiān)聽到一個寫操作線執(zhí)行完畢后立刻顯示操作結(jié)果,效果等同于Thread.VolatileRead if (count==1) { Console.WriteLine("累計計數(shù):{1}", Thread.CurrentThread.ManagedThreadId, value); //將count設(shè)置成0,等待另一個線程執(zhí)行完畢 count = 0; } } } }??????????? 從例子中大家可以看出Volatile關(guān)鍵字的出現(xiàn)替代了原先VolatileRead 和VolatileWrite方法的繁瑣,同時原子性的操作更加直觀透明??????
?
3.詳解Interlocked
?????? 相信大家理解了Volatile后對于非阻止同步和原子操作有了更深的認識,接下來的Interlocked雖然也屬于非阻止同步但是而后Volatile相比也
有著很大的不同,interlocked 利用了一個計數(shù)值的概念來實現(xiàn)同步,當(dāng)然這個計數(shù)值也是屬于原子性的操作,每個線程都有機會通過Interlocked
去遞增或遞減這個計數(shù)值來達到同步的效果,同時Interlocked比Volatile更加適應(yīng)復(fù)雜的邏輯和并發(fā)的情況
首先讓我們了解下Interlocked類的一些重要方法
| ?static long Read() 以原子操作形式讀取計數(shù)值,該方法能夠讀取當(dāng)前計數(shù)值,但是如果是64位cpu的可以不需要使用該方法讀取. *但是如果是32位的cpu則必須使用interlocked類的方法對64位的變量進行操作來保持原子操作,否則就不是原子操作 static int or long Increment(Int32 Or Int64) 該方法已原子操作的形式遞增指定變量的值并存儲結(jié)果,也可以理解成以原子的操作對計數(shù)器加1 Increment有2個返回類型的版本,分別是int 和 long static int or long??Decrement(Int32 Or Int64) 和Increment方法相反,該方法已原子操作的形式遞減指定變量的值并存儲結(jié)果,也可以理解成以原子的操作對計數(shù)器減1 同樣,Decrement也有2個返回類型的版本,分別是int 和 long static int Add(ref int location1,int value) 該方法是將Value的值和loation1中的值相加替換location1中原有值并且存儲在locaion1中,注意,該方法不會拋出溢出異常, 如果location中的值和Value之和大于int32.Max則,location1中的值會變成int32.Min和Value之和 Exchange(double location1,double value) Exchange方法有多個重載,但是使用方法是一致的,以原子操作的形式將Value的值賦值給location1 |
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
看完了概念性的介紹后,讓我們馬上進入很簡單的一個示例,來深刻理解下Interlocked的使用方法
/// <summary>/// 本示例通過Interlocked實現(xiàn)同步示例,通過Interlocked.Increment和/// Interlocked.Decrement來實現(xiàn)同步,此例有2個共享區(qū),一個必須滿足計數(shù)值為0,另 /// 一個滿足計數(shù)值為1時才能進入 /// </summary> class Program { //聲明計數(shù)變量 //(注意這里用的是long是64位的,所以在32位機子上一定要通過Interlocked來實現(xiàn)原子操作) static long _count = 0; static void Main(string[] args) { //開啟6個線程,3個執(zhí)行Excution1,三個執(zhí)行Excution2 for (int i = 0; i < 3; i++) { Thread thread = new Thread(new ThreadStart(Excution1)); Thread thread2 = new Thread(new ThreadStart(Excution2)); thread.Start(); Thread.Sleep(10); thread2.Start(); Thread.Sleep(10); } //這里和同步無關(guān),只是簡單的對Interlocked方法進行示例 Interlocked.Add(ref _count, 2); Console.WriteLine("為當(dāng)前計數(shù)值加上一個數(shù)量級:{0}后,當(dāng)前計數(shù)值為:{1}", 2, _count); Interlocked.Exchange(ref _count, 1); Console.WriteLine("將當(dāng)前計數(shù)值改變后,當(dāng)前計數(shù)值為:{0}", _count); Console.Read(); } static void Excution1() { //進入共享區(qū)1的條件 if (Interlocked.Read(ref _count) == 0) { Console.WriteLine("Thread ID:{0} 進入了共享區(qū)1", Thread.CurrentThread.ManagedThreadId); //原子性增加計數(shù)值,讓其他線程進入共享區(qū)2 Interlocked.Increment(ref _count); Console.WriteLine("此時計數(shù)值Count為:{0}", Interlocked.Read(ref _count)); } } static void Excution2() { //進入共享區(qū)2的條件 if (Interlocked.Read(ref _count) == 1) { Console.WriteLine("Thread ID:{0} 進入了共享區(qū)2", Thread.CurrentThread.ManagedThreadId); //原子性減少計數(shù)值,讓其他線程進入共享區(qū)1 Interlocked.Decrement(ref _count); Console.WriteLine("此時計數(shù)值Count為:{0}", Interlocked.Read(ref _count)); } } }在本例中,我們使用和上文一樣的思路,通過不同線程來原子性的操作計數(shù)值來達到同步效果,大家可以仔細觀察到,通過
Interlocked對計數(shù)值進行操作就能夠讓我們非常方便的使用非阻止的同步效果了,但是在復(fù)雜的項目或邏輯中,可能也會出
錯導(dǎo)致活鎖的可能,大家務(wù)必當(dāng)心
?
4.介紹下Lock關(guān)鍵字
Lock關(guān)鍵字是用來對于多線程中的共享區(qū)進行阻止同步的一種方案,當(dāng)某一個線程進入臨界區(qū)時,lock關(guān)鍵字會鎖住共享區(qū),
同樣可以理解為互斥段,互斥段在某一時刻內(nèi)只允許一個線程進入,同時編譯器會把這個關(guān)鍵字編譯成Monitor.Entery和
Monitor.Exit 方法,關(guān)于Monitor類會在下章詳細闡述。既然有Lock關(guān)鍵字,那么它是如何工作的?到底鎖住了什么,怎么
高效和正確的使用lock關(guān)鍵字呢?
?? 其實鎖的概念還是來自于現(xiàn)實生活,共享區(qū)就是多個人能夠共同擁有房間,當(dāng)其中一個人進入房間后,他把鎖反鎖,直到他解鎖
出門后將鑰匙交給下個人,可能房間的門可能有問題,或者進入房間的人因為某種原因出不來了,導(dǎo)致全部的人都無法進去,這些
問題也是我們應(yīng)該考慮到的,好,首先讓我們討論下我們應(yīng)該Lock住什么,什么材料適合當(dāng)鎖呢?
雖然說lock關(guān)鍵字可以鎖住任何object類型及其派生類,但是盡量不要用public 類型的,因為public類型難以控制
有可能大伙對上面的有點疑問,為什么不能用public類型的呢,為什么會難以控制呢?
好,以下3個例子是比較經(jīng)典的例證
| 1.Lock(this):大伙肯定會知道this指的是當(dāng)前類對象,Lock(this) 、意味著將當(dāng)前類對象給鎖住了, 假設(shè)我需要同時使用這個類的別的方法,那么某一線程一旦進入臨界區(qū)后,那完蛋了,該類所有的 成員(方法)都無法訪問,這可能在某些時刻是致命的錯誤 2.同理Lock(typeof(XXX)) 更厲害,一方面對鎖的性能有很大影響,因為一個類型太大了,其次, 當(dāng)某一線程進入臨界區(qū)后,包括所有該類型的type都可能會被鎖住而產(chǎn)生死鎖 3.最嚴重的某過于鎖住字符串對象,lock(“Test”),c#中的字符串對象很特殊,string? test=”Test” 和 string test2=”Test” 其實是一個對象,假如你使用了lock(“Test”)那么,所有字符串值為"Test"的 字符串都有可能被鎖住,甚至造成死鎖,所以有些奇怪的bug都是因為一些簡單的細節(jié)導(dǎo)致 |
?
?
?
?
?
?
?
?
?
接著這個例子便是lock(this)的一個示例,既能讓大伙了解如何使用Lock關(guān)鍵字,更是讓大伙了解,lock(this)的危害性
/// <summary>/// 本例展示下如何使用lock關(guān)鍵字和lock(this)時產(chǎn)生死鎖的情況/// </summary> class Program { static void Main(string[] args) { //創(chuàng)建b對象,演示lock B b = new B(); Console.ReadKey(); } } /// <summary> /// A類構(gòu)造中初始化一個線程并且啟動, /// 線程調(diào)用的方法內(nèi)放入死循環(huán),并且在死循環(huán)中放入lock(this), /// </summary> public class A { public A() { Thread th = new Thread(new ThreadStart ( () => { while (true) { lock (this) { Console.WriteLine("進入a類共享區(qū)"); Thread.Sleep(3000); } } } )); th.Start(); } } /// <summary> /// B類在構(gòu)造中創(chuàng)建A的對象,并且還是鎖住a對象,這樣就創(chuàng)建的死鎖的條件 /// 因為初始化A類對象時,A類的構(gòu)造函數(shù)會鎖住自身對象,這樣在A類死循環(huán)間隔期,一旦出了 A類中的鎖時 /// 進入B的鎖住的區(qū)域內(nèi),A 對象永遠無法進入a類共享區(qū),從而產(chǎn)生了死鎖 /// </summary> public class B { public B() { A a = new A(); lock (a) { Console.WriteLine(@"將a類對象鎖住的話,a類中的lock將進入死鎖, 直到3秒后B類中的將a類對象釋放鎖,如果我不釋放,那么 a類中將永遠無法進入a類共享區(qū)"); //計時器 Timer timer = new Timer(new TimerCallback ( (obj) => { Console.WriteLine(DateTime.Now); } ), this, 0, 1000); //如果這里運行很長時間, a類中將永遠無法進入a類共享區(qū) Thread.Sleep(3000000); } } }結(jié)果計時器會一直滾動,因為a對象被鎖住,除非完成Thread.Sleep(3000000)后才能進入到a共享區(qū)
由于以上的問題,微軟還是建議我們使用一個私有的變量來鎖定,由于私有變量外界無法訪問,所以鎖住話死鎖的可能性大大下降了。
這樣我們就能選擇正確的“門”來進行鎖住,但是可能還有一種可能也會造成死鎖,就是在lock內(nèi)部出現(xiàn)了問題,由于死鎖非常復(fù)雜,我將在
今后的文章中專門寫一篇關(guān)于死鎖的文章來深入解釋下死鎖,所以這里就對死鎖不深究了,這里大伙了解下lock的使用方法和注意事項就行了。
?
5.ReaderWriterLock
由于lock關(guān)鍵字對臨界區(qū)(共享區(qū))保護的非常周密,導(dǎo)致了一些功能可能會無法實現(xiàn),假設(shè)我將某個查詢功能放置在臨界區(qū)中時,可能
當(dāng)別的線程在查詢臨界區(qū)中的數(shù)據(jù)時,可能我的那個線程被阻塞了,所以我們期望鎖能夠達到以下功能
1 首先鎖能細分為讀鎖和寫鎖
2 能夠保證同時可以讓多個線程讀取數(shù)據(jù)
3 能保證同一時刻只有一個線程能進行寫操作,也就是說,對于寫操作,它必須擁有獨占鎖
4 能保證一個線程同一時刻只能擁有寫鎖或讀鎖中的一個
顯然lock關(guān)鍵字無法滿足我們的需求,還好微軟想到了這點,ReaderWriterLock便隆重登場了ReaderWriterLock能夠達到的效果是:
1. 同一時刻,它允許多個讀線程同時訪問臨界區(qū),或者允許單個線程進行寫訪問
2. 在讀訪問率很高,而且寫訪問率很低的情況下,效率最高,
3.它也滿足了同一時刻只能獲取寫鎖或讀鎖的要求。
4. 最為關(guān)鍵的是,ReaderWriterLock能夠保證讀線程鎖和寫線程鎖在各自的讀寫隊列中,當(dāng)某個線程釋放了寫鎖了,同時讀線程隊列中
的所有線程將被授予讀鎖,同樣,當(dāng)所有的讀鎖被釋放時,寫線程隊列中的排隊的下一個線程將被授予寫鎖,更直觀的說,ReaderWriterLock
就是在這幾種狀態(tài)間來回切換
5 使用時注意每當(dāng)你使用AcquireXXX方法獲取鎖時,必須使用ReleaseXXX方法來釋放鎖
6 ReaderWriterLock 支持遞歸鎖,關(guān)于遞歸鎖會在今后的章節(jié)詳細闡述
7 在性能方面ReaderWriterLock做的不夠理想,和lock比較差距明顯,而且該類庫中還隱藏些bug,有于這些原因,微軟又專門重新寫了個新
類ReaderWriterLockSilm來彌補這些缺陷。
8 處理死鎖方面ReaderWriterLock為我們提供了超時的參數(shù)這樣我們便可以有效的防止死鎖
9 對于一個個獲取了讀鎖的線程來說,在寫鎖空閑的情況下可以升級為寫鎖
?
接著讓我們了解下ReaderWriterLock的重要成員
上述4個方法分別是讓線程獲取寫鎖和讀鎖的方法,它利用的計數(shù)的概念,當(dāng)一個線程中調(diào)用此方法后,該類會給該線程擁有的鎖計數(shù)加1
(每次加1,但是一個線程可以擁有多個讀鎖,所以計數(shù)值可能更多,但是對于寫鎖來說同時一個一個線程可以擁有)。后面的參數(shù)是超時
時間,我們可以自己設(shè)置來避免死鎖。同樣調(diào)用上述方法后我們必須使用ReleaseXXX 方法來讓計數(shù)值減1,直到該線程擁有鎖的計數(shù)為0,
釋放了鎖為止。
?最后我們用一個簡單的例子來溫故下上述的知識點(請注意看注釋)
/// <summary>/// 該示例通過ReaderWriterLock同步來實現(xiàn)Student集合多線程下/// 的寫操作和讀操作 /// </summary> class Program { static ReaderWriterLock _readAndWriteLock = new ReaderWriterLock(); static List<Student> demoList = new List<Student>(); static void Main(string[] args) { InitialStudentList(); Thread thread=null; for (int i = 0; i <5; i++) { //讓第前2個個線程試圖掌控寫鎖, if (i < 2) { thread = new Thread(new ParameterizedThreadStart(AddStudent)); Console.WriteLine("線程ID:{0}, 嘗試獲取寫鎖 ", thread.ManagedThreadId); thread.Start(new Student { Name = "Zhang" + i }); } else { //讓每個線程都能訪問DisplayStudent 方法去獲取讀鎖 thread = new Thread(new ThreadStart(DisplayStudent)); thread.Start(); } Thread.Sleep(20); } Console.ReadKey(); } static void InitialStudentList() { demoList = new List<Student> { new Student{ Name="Sun"}, new Student{Name="Zheng"} }; } /// <summary> /// 當(dāng)多個線程試圖使用該方法時,只有一個線程能夠透過AcquireSWriterLock /// 獲取寫鎖,同時其他線程進入隊列中等待,直到該線程使用ReleaseWriterLock后 /// 下個線程才能進入擁有寫鎖 /// </summary> /// <param name="student"></param> static void AddStudent(object student) { if (student == null|| !(student is Student)) return; if (demoList.Contains(student)) return; try { //獲取寫鎖 _readAndWriteLock.AcquireWriterLock(Timeout.Infinite); demoList.Add(student as Student); Console.WriteLine("當(dāng)前寫操作線程為{0}, 寫入的學(xué)生是:{1}", Thread.CurrentThread.ManagedThreadId,(student as Student).Name); } catch (Exception) { } finally { _readAndWriteLock.ReleaseWriterLock(); } } /// <summary> /// 對于讀鎖來所,允許多個線程共同擁有,所以這里同時 /// 可能會有多個線程訪問Student集合,使用try catch是為了 /// 一定要讓程序執(zhí)行finally語句塊中的releaseXXX方法,從而保證 /// 能夠釋放鎖 /// </summary> static void DisplayStudent() { try { _readAndWriteLock.AcquireReaderLock(Timeout.Infinite); demoList.ForEach(student => { Console.WriteLine("當(dāng)前集合中學(xué)生為:{0},當(dāng)前讀操作線程為{1}", student.Name, Thread.CurrentThread.ManagedThreadId); }); } catch (Exception) { } finally { _readAndWriteLock.ReleaseReaderLock(); } } } internal class Student { public string Name { get; set; } }?運行結(jié)果:
從例子可以看出有2個線程試圖嘗試爭取寫鎖,但是同時只有一個線程可以獲取到寫鎖,同時對于讀取集合的線程可以同時獲取多個讀鎖
?
6.??本章總結(jié)
?由于本人上個月工作突然忙了起來,快一個多月沒更新博客了,希望大家可以見諒^^
?本章介紹了線程同步的概念和一些關(guān)于同步非常重要的基本概念,對于原子性的操作的認識也格外重要,同時對于Volatile,Interlocked,lock,ReaderWriterLock 知識點做了相關(guān)介紹,
相信大家對于線程同步有個初步的認識和理解,在寫本篇博客時,發(fā)現(xiàn)死鎖也是個很重要的知識點,關(guān)于死鎖我會單獨寫篇文章來闡述,謝謝大家的支持!
?
7.??參考資料?
?CLR via c#
?msdn
轉(zhuǎn)載于:https://www.cnblogs.com/yourshj/p/5217593.html
總結(jié)
- 上一篇: Android开发之IPC进程间通信-A
- 下一篇: 【转】路由转发过程的IP及MAC地址变化