您好,登錄后才能下訂單哦!
.Net組件程序設(shè)計(jì)之線(xiàn)程、并發(fā)管理(二)
所有的.NET組件都支持在多線(xiàn)程的環(huán)境中運(yùn)行,可以被多個(gè)線(xiàn)程并發(fā)訪(fǎng)問(wèn),如果沒(méi)有線(xiàn)程同步,這樣的后果是當(dāng)多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn) 對(duì)象狀態(tài)時(shí),對(duì)象的狀態(tài)可能被破壞,造成不一致性。.NET提供了兩種方法來(lái)避免這樣的問(wèn)題,使得我們?cè)O(shè)計(jì)的組件更加健壯。 第一種是自動(dòng)同步,讓你使用一個(gè)屬性來(lái)修飾組件,這樣就可以把組件交給.NET了,同步的事情也就交給了.NET。 第二種是手動(dòng)同步,這是讓你使用.NET提供的同步對(duì)象來(lái)實(shí)現(xiàn)線(xiàn)程同步,也不是太復(fù)雜,本篇將會(huì)對(duì)手動(dòng)同步來(lái)稍作講解。
.NET手動(dòng)同步提供了一套豐富的同步鎖,上一節(jié)說(shuō)到同步域,同步域事實(shí)上是一個(gè)巨大的宏鎖,而手動(dòng)同步則提供了 對(duì)被鎖對(duì)象的細(xì)粒度控制,可以控制訪(fǎng)問(wèn)對(duì)象、單一成員甚至是單行的代碼。這樣的好處就是有可能的提高系統(tǒng)的性能和吞吐量。
監(jiān)視器是一種只能和引用類(lèi)型一塊工作的鎖。
2.1.1-1
1 public class ManualSynchronization 2 { 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 try 5 { 6 monitorcase.DoSomeThing(); 7 } 8 finally 9 { 10 Monitor.Exit(monitorcase); 11 }
任何線(xiàn)程的任何對(duì)象都可以調(diào)用Enter()方法來(lái)鎖定對(duì)象,如果Monitor正在被一個(gè)線(xiàn)程使用,而這個(gè)時(shí)候又有一個(gè)線(xiàn)程來(lái)請(qǐng)求對(duì)象Enter(),這樣就會(huì)使第二個(gè)線(xiàn)程阻塞,直到第一個(gè)線(xiàn)程調(diào)用Exit(),如果這時(shí)有多個(gè)線(xiàn)程請(qǐng)求對(duì)象Enter(),它們就會(huì)被放置在一個(gè)叫做鎖隊(duì)列的隊(duì)列里,并依照隊(duì)列的順序獲得服務(wù)順序。
你還可以使用Monitor類(lèi)為靜態(tài)類(lèi)方法或靜態(tài)屬性提供安全線(xiàn)程訪(fǎng)問(wèn),方法是讓Monitor鎖定該類(lèi)型,而不是一個(gè)實(shí)例:
2.1.1-2
1 public class ManualSynchronization 2 { 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#中為了簡(jiǎn)化這樣的寫(xiě)法,提供了lock語(yǔ)句,使編譯器在try/finally語(yǔ)句中自動(dòng)產(chǎn)生對(duì)Enter()和Exit()的調(diào)用。
比如你寫(xiě)下這樣的代碼等同于2.1.1-1的示例代碼:
2.1.1-3
1 ManualSynchronization monitorcase = new ManualSynchronization(); 2 lock(monitorcase) 3 { 4 monitorcase.DoSomeThing(); 5 }
像上面的代碼這樣寫(xiě)看似沒(méi)什么問(wèn)題了,因?yàn)檫@個(gè)lock所定對(duì)象實(shí)例或者是對(duì)象類(lèi)型,是根據(jù)客戶(hù)端開(kāi)發(fā)者的判斷而定的,這樣的鎖定方式與客戶(hù)端耦合度大,看下以下代碼:
2.1.1-4
1 public class ManualSynchronization 2 { 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();
這樣感覺(jué)是不是舒服不少,這就是方法同步了,.NET內(nèi)部也對(duì)它提供了支持,定義在System.Runtime.CompilerServices命名空間里的MethodImpl方法屬性接受一個(gè)MethodImplOptions類(lèi)型的枚舉。其中一個(gè)枚舉值是MethodImplOptions.Synchronized。當(dāng)運(yùn)行這個(gè)枚舉值的時(shí)候,編輯器就指示.NET運(yùn)行時(shí)在方法入口鎖定對(duì)象,語(yǔ)義和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 }
這一個(gè)小節(jié)要講到的是Mutex類(lèi),它是從WaitHandle派生的類(lèi),它保證了各個(gè)線(xiàn)程在某個(gè)資源或代碼塊上相互排斥。
2.1.2-1
1 public class MutexDom:IDisposable 2 { 3 public MutexDom(){} 4 private int _Num = 0; 5 public int Num 6 { 7 get 8 { 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();啟動(dòng)測(cè)試,我所希望的效果是Dom()方法是有序的執(zhí)行的,而我用了一個(gè)int類(lèi)型的Nun屬性來(lái)作為計(jì)數(shù)器,那我們就一起來(lái)看一下結(jié)果吧(可能每次運(yùn)行結(jié)果不一樣)
我所期望的在線(xiàn)程Thread_One中執(zhí)行0遞增至99的值時(shí)4950,而在結(jié)果中已經(jīng)超出了這個(gè)范圍,這說(shuō)明了什么?說(shuō)明了兩個(gè)線(xiàn)程在交替的對(duì)Num進(jìn)行操作。修改一下代碼,再來(lái)看一下:
2.1.2-2
1 public class MutexDom:IDisposable 2 { 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();//如果當(dāng)前資源被占用 則等待占用它的線(xiàn)程發(fā)送消息 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 }
從結(jié)果中得出,是線(xiàn)程Thread_Two先執(zhí)行的,這個(gè)沒(méi)關(guān)系,只要看它的結(jié)果值就行了,這就說(shuō)明了,在線(xiàn)程"Thread_Two"執(zhí)行對(duì)Dom()方法操作的時(shí)候"Thread_One"是肯定已經(jīng)啟動(dòng)了的,而且是在等待"Thread_Two"的釋放消息,這樣就保持了對(duì)象狀態(tài)的一致性,這個(gè)時(shí)候"Thread_One"是在一個(gè)等待隊(duì)列中的。如果這個(gè)時(shí)候"Thread_One"調(diào)用ReleaseMutex()方法,是會(huì)報(bào)錯(cuò)的,因?yàn)镽eleaseMutex()方法是只能當(dāng)前所占有的線(xiàn)程來(lái)進(jìn)行釋放,互斥就這樣完成了。
EventWaitHandle類(lèi)派生于WaitHandle,被用于跨線(xiàn)程通知事件。 它有兩種狀態(tài):信號(hào)已發(fā)狀態(tài)、信號(hào)未發(fā)狀態(tài)。 Set()方法和 Reset()方法分別把句柄狀態(tài)設(shè)置為信號(hào)已發(fā)或信號(hào)未發(fā)。 它有兩種使用方式,一種是手動(dòng)重置,還有一種是自動(dòng)重置。是通過(guò)給構(gòu)造函數(shù)提供一個(gè)EventResetMode類(lèi)型的枚舉值,
1 public enum EventResetMode 2 { 3 AutoReset, 4 ManualReset 5 }
.NET提供了EventWaitHandle的兩個(gè)強(qiáng)類(lèi)型子類(lèi),定義如下:
1 public class ManualResetEvent:EventWaitHandle 2 { 3 public ManualResetEvent(bool initialState):base(initialState,EventResetMode.ManualReset) 4 {} 5 } 6 public sealed class AutoResetEvent : EventWaitHandle 7 { 8 public AutoResetEvent(bool initialState):base(initialState,EventResetMode.AutoReset) 9 {} 10 }
先來(lái)看一下手動(dòng)重置:
2.1.3-1
1 public class EventDom:IDisposable 2 { 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 }
調(diào)用EventDom.Test();進(jìn)行測(cè)試,結(jié)果如下圖:
在構(gòu)造函數(shù)中我就已經(jīng)把手動(dòng)重置事件聲明為了 信號(hào)已發(fā)狀態(tài),所以在運(yùn)行的時(shí)候,while在每次循環(huán)的時(shí)候等待接收到的信號(hào)一直都是已發(fā)送狀態(tài),所以是一直在輸出,直到調(diào)用了StopThread()方法中的Reset()方法,把狀態(tài)設(shè)置為未發(fā)送狀態(tài),才使執(zhí)行暫停。
再來(lái)看一下自動(dòng)重置,修改一下上段的代碼,
1 public class EventDom : IDisposable 2 { 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 }
首先把手動(dòng)重置類(lèi)型換成了自動(dòng)重置類(lèi)型,然后再測(cè)試代碼中把設(shè)置狀態(tài)為未發(fā)送的方法,改成了設(shè)置狀態(tài)為已發(fā)送的方法。
這個(gè)結(jié)果是正確,因?yàn)樽詣?dòng)重置類(lèi)型就是事件狀態(tài)被設(shè)置為信號(hào)已發(fā),它就會(huì)保持這個(gè)狀態(tài),直到某個(gè)線(xiàn)程從等待調(diào)用中釋放出來(lái),然后在這個(gè)時(shí)候,它的狀態(tài)會(huì)發(fā)生改變,自動(dòng)的反轉(zhuǎn)到未發(fā)送狀態(tài)。
還有一些擴(kuò)展的知識(shí)點(diǎn)就不在這一一闡述了,希望本篇能對(duì)大家有所幫助。END
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guā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)容。