.Net組件程序設計之線程、並發管理(二)
所有的.NET組件都支持在多線程的環境中運行,可以被多個線程並發訪問,如果沒有線程同步,這樣的後果是當多個線程同時訪問 對象狀態時,對象的狀態可能被破壞,造成不一致性。.NET提供了兩種方法來避免這樣的問題,使得我們設計的組件更加健壯。 第一種是自動同步,讓你使用一個屬性來修飾組件,這樣就可以把組件交給.NET了,同步的事情也就交給了.NET。 第二種是手動同步,這是讓你使用.NET提供的同步對象來實現線程同步,也不是太復雜,本篇將會對手動同步來稍作講解。
.NET手動同步提供了一套豐富的同步鎖,上一節說到同步域,同步域事實上是一個巨大的宏鎖,而手動同步則提供了 對被鎖對象的細粒度控制,可以控制訪問對象、單一成員甚至是單行的代碼。這樣的好處就是有可能的提高系統的性能和吞吐量。
監視器是一種只能和引用類型一塊工作的鎖。
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 }
任何線程的任何對象都可以調用Enter()方法來鎖定對象,如果Monitor正在被一個線程使用,而這個時候又有一個線程來請求對象Enter(),這樣就會使第二個線程阻塞,直到第一個線程調用Exit(),如果這時有多個線程請求對象Enter(),它們就會被放置在一個叫做鎖隊列的隊列裡,並依照隊列的順序獲得服務順序。
你還可以使用Monitor類為靜態類方法或靜態屬性提供安全線程訪問,方法是讓Monitor鎖定該類型,而不是一個實例:
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#中為了簡化這樣的寫法,提供了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 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();
這樣感覺是不是舒服不少,這就是方法同步了,.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 }
這一個小節要講到的是Mutex類,它是從WaitHandle派生的類,它保證了各個線程在某個資源或代碼塊上相互排斥。
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();啟動測試,我所希望的效果是Dom()方法是有序的執行的,而我用了一個int類型的Nun屬性來作為計數器,那我們就一起來看一下結果吧(可能每次運行結果不一樣)

我所期望的在線程Thread_One中執行0遞增至99的值時4950,而在結果中已經超出了這個范圍,這說明了什麼?說明了兩個線程在交替的對Num進行操作。修改一下代碼,再來看一下:
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();//如果當前資源被占用 則等待占用它的線程發送消息
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()方法是只能當前所占有的線程來進行釋放,互斥就這樣完成了。
EventWaitHandle類派生於WaitHandle,被用於跨線程通知事件。 它有兩種狀態:信號已發狀態、信號未發狀態。 Set()方法和 Reset()方法分別把句柄狀態設置為信號已發或信號未發。 它有兩種使用方式,一種是手動重置,還有一種是自動重置。是通過給構造函數提供一個EventResetMode類型的枚舉值,
1 public enum EventResetMode
2 {
3 AutoReset,
4 ManualReset
5 }
.NET提供了EventWaitHandle的兩個強類型子類,定義如下:
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 }
先來看一下手動重置:
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 }
調用EventDom.Test();進行測試,結果如下圖:

在構造函數中我就已經把手動重置事件聲明為了 信號已發狀態,所以在運行的時候,while在每次循環的時候等待接收到的信號一直都是已發送狀態,所以是一直在輸出,直到調用了StopThread()方法中的Reset()方法,把狀態設置為未發送狀態,才使執行暫停。
再來看一下自動重置,修改一下上段的代碼,
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 }
首先把手動重置類型換成了自動重置類型,然後再測試代碼中把設置狀態為未發送的方法,改成了設置狀態為已發送的方法。

這個結果是正確,因為自動重置類型就是事件狀態被設置為信號已發,它就會保持這個狀態,直到某個線程從等待調用中釋放出來,然後在這個時候,它的狀態會發生改變,自動的反轉到未發送狀態。
還有一些擴展的知識點就不在這一一闡述了,希望本篇能對大家有所幫助。END
作者:金源
出處:http://www.cnblogs.com/jin-yuan/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面