.Net组件程序设计之线程、并发管理(二)
?.Net組件程序設計之線程、并發管理(二)
2.同步線程
手動同步
監視器
互斥
可等待事件
?
同步線程
所有的.NET組件都支持在多線程的環境中運行,可以被多個線程并發訪問,如果沒有線程同步,這樣的后果是當多個線程同時訪問 對象狀態時,對象的狀態可能被破壞,造成不一致性。.NET提供了兩種方法來避免這樣的問題,使得我們設計的組件更加健壯。 第一種是自動同步,讓你使用一個屬性來修飾組件,這樣就可以把組件交給.NET了,同步的事情也就交給了.NET。 第二種是手動同步,這是讓你使用.NET提供的同步對象來實現線程同步,也不是太復雜,本篇將會對手動同步來稍作講解。
?
2.1 手動同步
??? .NET手動同步提供了一套豐富的同步鎖,上一節說到同步域,同步域事實上是一個巨大的宏鎖,而手動同步則提供了 對被鎖對象的細粒度控制,可以控制訪問對象、單一成員甚至是單行的代碼。這樣的好處就是有可能的提高系統的性能和吞吐量。
2.1.1 監視器
監視器是一種只能和引用類型一塊工作的鎖。
2.1.1-1
?1?????public?class?ManualSynchronization2?????{3?????????public?void?DoSomeThing()4?????????{5?????????????for?(int?i?=?0;?i?<?100;?i++)6?????????????{7?????????????????Console.WriteLine(i.ToString());8?????????????}9?????????} 10?????}?1?ManualSynchronization?monitorcase?=?new?ManualSynchronization();2?3?Monitor.Enter(monitorcase);4?try5?{6?????monitorcase.DoSomeThing();7?}8?finally9?{ 10?????Monitor.Exit(monitorcase); 11?}任何線程的任何對象都可以調用Enter()方法來鎖定對象,如果Monitor正在被一個線程使用,而這個時候又有一個線程來請求對象Enter(),這樣就會使第二個線程阻塞,直到第一個線程調用Exit(),如果這時有多個線程請求對象Enter(),它們就會被放置在一個叫做鎖隊列的隊列里,并依照隊列的順序獲得服務順序。
你還可以使用Monitor類為靜態類方法或靜態屬性提供安全線程訪問,方法是讓Monitor鎖定該類型,而不是一個實例:
2.1.1-2
?1?????public?class?ManualSynchronization2?????{3?????????public?static?void?SDoSomeThing()4?????????{5?????????????for?(int?i?=?0;?i?<?100;?i++)6?????????????{7?????????????????Console.WriteLine(i.ToString());8?????????????}9?????????} 10?????}1????????????Monitor.Enter(typeof(ManualSynchronization)); 2????????????try 3????????????{ 4????????????????ManualSynchronization.SDoSomeThing(); 5????????????} 6????????????finally 7????????????{ 8????????????????Monitor.Exit(typeof(ManualSynchronization)); 9????????????}在C#中為了簡化這樣的寫法,提供了lock語句,使編譯器在try/finally語句中自動產生對Enter()和Exit()的調用。
比如你寫下這樣的代碼等同于2.1.1-1的示例代碼:
2.1.1-3
1?ManualSynchronization?monitorcase?=?new?ManualSynchronization(); 2?lock(monitorcase) 3?{ 4?????monitorcase.DoSomeThing(); 5?}像上面的代碼這樣寫看似沒什么問題了,因為這個lock所定對象實例或者是對象類型,是根據客戶端開發者的判斷而定的,這樣的鎖定方式與客戶端耦合度大,看下以下代碼:
2.1.1-4
?1?????public?class?ManualSynchronization2?????{3?????????public?void?DoSomeThing()4?????????{5?????????????lock?(this)6?????????????{7?????????????????for?(int?i?=?0;?i?<?100;?i++)8?????????????????{9?????????????????????Console.WriteLine(i.ToString()); 10?????????????????} 11?????????????} 12?????????} 13?????}1?ManualSynchronization?monitorcase?=?new?ManualSynchronization(); 2?monitorcase.DoSomeThing();這樣感覺是不是舒服不少,這就是方法同步了,.NET內部也對它提供了支持,定義在System.Runtime.CompilerServices命名空間里的MethodImpl方法屬性接受一個MethodImplOptions類型的枚舉。其中一個枚舉值是MethodImplOptions.Synchronized。當運行這個枚舉值的時候,編輯器就指示.NET運行時在方法入口鎖定對象,語義和2.1.1-4的代碼斷相同:
2.1.1-5
1?????public?class?ManualSynchronization 2?????{ 3?????????[MethodImpl(?MethodImplOptions.Synchronized)] 4?????????public?void?DoSomeThingSynchroniezd() 5?????????{ 6?????????????Console.WriteLine("studycase"); 7?????????} 8?????}2.1.2 互斥
這一個小節要講到的是Mutex類,它是從WaitHandle派生的類,它保證了各個線程在某個資源或代碼塊上相互排斥。
?2.1.2-1
?1?????public?class?MutexDom:IDisposable2?????{3?????????public?MutexDom(){}4?????????private?int?_Num?=?0;5?????????public?int?Num6?????????{7?????????????get8?????????????{9?????????????????return?_Num; 10?????????????} 11?????????????set 12?????????????{ 13?????????????????_Num?=?value; 14?????????????} 15?????????} 16?????????public?void?Dom() 17?????????{ 18??????????????????for?(int?i?=?0;?i?<?100;?i++) 19?????????????????{ 20?????????????????????Num?=?Num?+?i; 21?????????????????????Console.WriteLine(Thread.CurrentThread.Name?+?"_"?+?Num.ToString()?+"_"+Thread.CurrentThread.ManagedThreadId.ToString()); 22?????????????????} 23? 24????????????? 25?????????} 26?????????public?void?Dispose() 27?????????{ 28?????????? 29?????????} 30? 31?????????public?static?void?Test() 32?????????{ 33?????????????MutexDom?mutexDom=new?MutexDom(); 34?????????????ThreadStart?threadStart=new?ThreadStart(mutexDom.Dom); 35?????????????Thread?thread1?=?new?Thread(threadStart); 36?????????????thread1.Name?=?"Thread_One"; 37?????????????Thread?thread2?=?new?Thread(threadStart); 38?????????????thread2.Name?=?"Thread_Two"; 39?????????????thread1.Start(); 40?????????????thread2.Start(); 41?????????} 42?????}MutexDom.Test();啟動測試,我所希望的效果是Dom()方法是有序的執行的,而我用了一個int類型的Nun屬性來作為計數器,那我們就一起來看一下結果吧(可能每次運行結果不一樣)
我所期望的在線程Thread_One中執行0遞增至99的值時4950,而在結果中已經超出了這個范圍,這說明了什么?說明了兩個線程在交替的對Num進行操作。修改一下代碼,再來看一下:
?2.1.2-2
?1?????public?class?MutexDom:IDisposable2?????{3?????????private?Mutex?_Mutex;4?????????public?MutexDom()5?????????{6?????????????_Mutex?=?new?Mutex();7?????????}8?????????private?int?_Num?=?0;9?????????public?int?Num 10?????????{ 11?????????????get 12?????????????{ 13?????????????????return?_Num; 14?????????????} 15?????????????set 16?????????????{ 17?????????????????_Num?=?value; 18?????????????} 19?????????} 20?????????public?void?Dom() 21?????????{ 22?????????????_Mutex.WaitOne();//如果當前資源被占用?則等待占用它的線程發送消息 23?????????????try 24?????????????{ 25?????????????????for?(int?i?=?0;?i?<?100;?i++) 26?????????????????{ 27?????????????????????Num?=?Num?+?i; 28?????????????????????Console.WriteLine(Thread.CurrentThread.Name?+?"_"?+?Num.ToString()?+"_"+Thread.CurrentThread.ManagedThreadId.ToString()); 29?????????????????} 30?????????????} 31?????????????finally 32?????????????{ 33?????????????????_Mutex.ReleaseMutex(); 34?????????????} 35????????????? 36?????????} 37?????????public?void?Dispose() 38?????????{ 39?????????????_Mutex.Close(); 40?????????} 41? 42?????????public?static?void?Test() 43?????????{ 44?????????????MutexDom?mutexDom=new?MutexDom(); 45?????????????ThreadStart?threadStart=new?ThreadStart(mutexDom.Dom); 46?????????????Thread?thread1?=?new?Thread(threadStart); 47?????????????thread1.Name?=?"Thread_One"; 48?????????????Thread?thread2?=?new?Thread(threadStart); 49?????????????thread2.Name?=?"Thread_Two"; 50????????????? 51?????????????thread1.Start(); 52?????????????thread2.Start(); 53????????????? 54?????????} 55?????}從結果中得出,是線程Thread_Two先執行的,這個沒關系,只要看它的結果值就行了,這就說明了,在線程"Thread_Two"執行對Dom()方法操作的時候"Thread_One"是肯定已經啟動了的,而且是在等待"Thread_Two"的釋放消息,這樣就保持了對象狀態的一致性,這個時候"Thread_One"是在一個等待隊列中的。如果這個時候"Thread_One"調用ReleaseMutex()方法,是會報錯的,因為ReleaseMutex()方法是只能當前所占有的線程來進行釋放,互斥就這樣完成了。
2.1.3 可等待事件
EventWaitHandle類派生于WaitHandle,被用于跨線程通知事件。 它有兩種狀態:信號已發狀態、信號未發狀態。 Set()方法和 Reset()方法分別把句柄狀態設置為信號已發或信號未發。 它有兩種使用方式,一種是手動重置,還有一種是自動重置。是通過給構造函數提供一個EventResetMode類型的枚舉值,
1?????public?enum?EventResetMode 2?????{ 3????????AutoReset, 4????????ManualReset 5?????}.NET提供了EventWaitHandle的兩個強類型子類,定義如下:
?1?????public?class?ManualResetEvent:EventWaitHandle2?????{3?????????public?ManualResetEvent(bool?initialState):base(initialState,EventResetMode.ManualReset)4?????????{}5?????}6?????public?sealed?class?AutoResetEvent?:?EventWaitHandle7?????{8?????????public?AutoResetEvent(bool?initialState):base(initialState,EventResetMode.AutoReset)9?????????{} 10?????}先來看一下手動重置:
2.1.3-1
?1?????public?class?EventDom:IDisposable2?????{3?????????ManualResetEvent?_WaitHandle;4?????????public?EventDom()5?????????{6?????????????_WaitHandle?=?new?ManualResetEvent(true);7?8?????????????Thread?thread?=?new?Thread(DoWork);9?????????????thread.Start(); 10?????????} 11?????????private?void?DoWork() 12?????????{ 13?????????????int?num?=?0; 14?????????????while?(true) 15?????????????{ 16?????????????????_WaitHandle.WaitOne(); 17?????????????????num++; 18?????????????????Console.WriteLine("EventDom_"?+?num.ToString()); 19?????????????} 20?????????} 21?????????public?void?StartThread() 22?????????{ 23?????????????_WaitHandle.Set(); 24?????????????Console.WriteLine("EventDom->StartThread"); 25?????????} 26?????????public?void?StopThread() 27?????????{ 28?????????????_WaitHandle.Reset(); 29?????????????Console.WriteLine("EventDom->StopThread"); 30?????????} 31?????????public?void?Dispose() 32?????????{ 33?????????????_WaitHandle.Close(); 34?????????} 35? 36?????????public?static?void?Test() 37?????????{ 38?????????????EventDom?eventDom?=?new?EventDom(); 39?????????????eventDom.StopThread(); 40?????????} 41? 42??????}?調用EventDom.Test();進行測試,結果如下圖:
在構造函數中我就已經把手動重置事件聲明為了 信號已發狀態,所以在運行的時候,while在每次循環的時候等待接收到的信號一直都是已發送狀態,所以是一直在輸出,直到調用了StopThread()方法中的Reset()方法,把狀態設置為未發送狀態,才使執行暫停。
再來看一下自動重置,修改一下上段的代碼,
?1?????public?class?EventDom?:?IDisposable2?????{3?????????AutoResetEvent?_WaitHandle;4?????????public?EventDom()5?????????{6?????????????_WaitHandle?=?new?AutoResetEvent(true);7?8?????????????Thread?thread?=?new?Thread(DoWork);9?????????????thread.Start(); 10?????????} 11?????????private?void?DoWork() 12?????????{ 13?????????????int?num?=?0; 14?????????????while?(true) 15?????????????{ 16?????????????????_WaitHandle.WaitOne(); 17?????????????????num++; 18?????????????????Console.WriteLine("EventDom_"?+?num.ToString()); 19?????????????} 20?????????} 21?????????public?void?StartThread() 22?????????{ 23?????????????_WaitHandle.Set(); 24?????????????Console.WriteLine("EventDom->StartThread"); 25?????????} 26?????????public?void?StopThread() 27?????????{ 28?????????????_WaitHandle.Reset(); 29?????????????Console.WriteLine("EventDom->StopThread"); 30?????????} 31?????????public?void?Dispose() 32?????????{ 33?????????????_WaitHandle.Close(); 34?????????} 35? 36?????????public?static?void?Test() 37?????????{ 38?????????????EventDom?eventDom?=?new?EventDom(); 39?????????????eventDom.StartThread(); 40?????????} 41?????}首先把手動重置類型換成了自動重置類型,然后再測試代碼中把設置狀態為未發送的方法,改成了設置狀態為已發送的方法。
這個結果是正確,因為自動重置類型就是事件狀態被設置為信號已發,它就會保持這個狀態,直到某個線程從等待調用中釋放出來,然后在這個時候,它的狀態會發生改變,自動的反轉到未發送狀態。
還有一些擴展的知識點就不在這一一闡述了,希望本篇能對大家有所幫助。END
?
?
?
轉載于:https://blog.51cto.com/jinyuan/1421966
總結
以上是生活随笔為你收集整理的.Net组件程序设计之线程、并发管理(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用Windows Live Writer
- 下一篇: 栈,堆,值类型,引用类型,装箱,拆箱 .