最近工作閑暇之際,翻閱了以前保存的電子書《C#多線程編程手冊》,發現此書同步技術這塊寫的甚好,於是參考此書並結合實例,對同步技術做一下總結和分析,也算是讀書筆記與心得體會吧,並與大家分享。
書中提到的同步技術有很多種,歸納起來常用的方式有以下幾種:
1、利用屬性標簽方式進行方法同步和上下文同步:MethodImplAttribute 類 和 SynchronizationAttribute 類
2、同步代碼區:Monitor 類、Lock 關鍵字、ReaderWriterLock 類。
3、手控同步:AutoResetEvent 類、ManualResetEvent 類、Mutex 類、Interlocked 類
后面的文章我們會依次對以上類或關鍵字進行介紹,首先我們先來說說Thread類中的Join方法,書中對它的介紹比較簡略,但是我覺得它也算是線程間同步方式的一種了,對它的用法也來總結和歸納一下。
MSDN對 Join 方法解釋說:在繼續執行標准的 COM 和 SendMessage 消息泵處理期間,阻塞調用線程,直到某個線程終止或經過了指定時間為止。
哈哈,MSDN的解釋永遠都是那么的繞嘴,我們還是通過一個簡單的控制台程序作為例子來說明一下吧:
首先我們建立一個計算類Calculate,里面包含一個加法線程 ThreadAdd 和一個加法方法,在Add()方法中並讓執行運算的線程休眠5秒,代碼如下:
//計算類 public class Calculate { public Thread threadAdd; public Calculate() { threadAdd = new Thread(new ThreadStart(Add)); } //加法運算 public void Add() { Console.ForegroundColor = ConsoleColor.Blue; Console.WriteLine("進入加法計算");
Thread.Sleep(5000); Console.ForegroundColor = ConsoleColor.Blue; Console.WriteLine("加法運算結果: x={0} y={1} x+y={2}", 1, 2, 1 + 2); } }
然后我們在Main方法中進行調用:
class Program { static void Main(string[] args) { Calculate calculate = new Calculate(); Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("主線程輸出:准備進行加法運算:"); calculate.threadAdd.Start(); Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("主線程輸出:運算完畢"); Console.ReadKey(); } }
運算結果如圖:
到這里,我們會發現執行Main() 方法的主線程,並沒有等待 執行加法的工作線程,而是直接輸出了“運算完畢”,這時候我們的Join() 方法就該上場了,我們對Main() 函數進行修改一下:
static void Main(string[] args) { Calculate calculate = new Calculate(); Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("主線程輸出:准備進行加法運算:"); calculate.threadAdd.Start(); //增加Join calculate.threadAdd.Join(); Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("主線程輸出:運算完畢"); Console.ReadKey(); }
運行結果如下圖:
這樣的結果是我們想要的正常輸出順序。運行的時候我們發現,當主線程執行到 calculate.threadAdd.Join(); 的時候,並沒有繼續執行,一直等到 加法線程 運算完畢之后主線程才繼續運行,這不就是和MSDN中解釋的一樣嗎?主線程現在就屬於調用線程,當主線程調用了calculate.threadAdd.Join()的時候,就發生了阻塞,直到加法線程運行完畢之后,才繼續運行。
現在我們在來看看Join的另外兩個重載方法:Join(Int32) 和 Join(TimeSpan),這兩個方法其實是一樣的,輸入參數說白了就是設置阻塞的等待時間,返回值是bool類型,如果線程已終止,則為 true,否則返回 false 。不明白沒關系,我們繼續來看例子:
我們修改一計算類,再增加一個 減法方法Sub() 和一個執行減法的線程ThreadSub,代碼如下:
//計算類 public class Calculate { public Thread threadAdd; public Thread threadSub; public Calculate() { threadAdd = new Thread(new ThreadStart(Add)); threadSub = new Thread(new ThreadStart(Sub)); } //加法運算 public void Add() { Console.ForegroundColor = ConsoleColor.Blue; Console.WriteLine("進入加法計算"); Thread.Sleep(5000); Console.ForegroundColor = ConsoleColor.Blue; Console.WriteLine("加法運算結果: x={0} y={1} x+y={2}", 1, 2, 1 + 2); } //新增減法運算 public void Sub() { //主要是這里 bool b = threadAdd.Join(1000); Console.ForegroundColor = ConsoleColor.Red; if (b) { Console.WriteLine("加法運算已經完成,進入減法法計算"); } else { Console.WriteLine("加法運算超時,先進入減法法計算"); } Thread.Sleep(2000); Console.WriteLine("進入減法運算"); Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("減法運算結果: x={0} y={1} x-y={2}", 10, 2, 10 - 2); } }
Main() 方法修改為:
class Program { static void Main(string[] args) { Calculate calculate = new Calculate(); Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("主線程輸出:准備進行加法和減法兩種運算:"); calculate.threadAdd.Start(); calculate.threadSub.Start(); calculate.threadAdd.Join(); calculate.threadSub.Join(); Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("主線程輸出:所有運算完畢"); Console.ReadKey(); } }
運行結果如下:
結果是正確的,我們來分析一下整個的運算過程:
首先,主線程遇到 calculate.threadAdd.Join(); 和 calculate.threadSub.Join(); 肯定會發生阻塞,等待這兩個線程完成后,才會繼續執行,這個不容質疑。然后我們看加法線程和減法線程,這兩個線程幾乎同時執行,誰先執行,我們是不可預期的。比如先執行加法線程,當執行到Thread.Sleep(5000),的時候,加法線程休眠5s,減法線程由於調用了 threadAdd.Join(1000); 所以減法線程會阻塞1s ,1s 之后由於加法線程還沒有執行完成,所以 返回值為 false,減法線程繼續執行,減法線程執行完畢后,又過了一會,加法線程才繼續執行。這樣就會得出我們上面的運行結果。
Thread.Join() 方法的用法這么多了,下一篇 來總結和說明一下 MethodImplAttribute 類 和 SynchronizationAttribute 類。