所謂線程同步,就是多個線程之間在某個對象上執行等待(也可理解為鎖定該對象),直到該對象被解除鎖定。C#中對象的類型分為引用類型和值類型。CLR在這兩種類型上的等待是不一樣的。我們可以簡單的理解為在CLR中,值類型是不能被鎖定的,也即:不能在一個值類型對象上執行等待。而在引用類型上的等待機制,則分為兩類:鎖定和信號同步。
鎖定,使用關鍵字lock和類型Monitor。兩者沒有實質區別,前者其實是後者的語法糖。這是最常用的同步技術;
本建議我們討論的是信號同步。信號同步機制中涉及的類型都繼承自抽象類WaitHandle,這些類型有EventWaitHandle(類型化為AutoResetEvent、ManualResetEvent)和Semaphore以及Mutex。見類圖6-3:
圖 同步功能類類圖
EventWaitHandle(子類為AutoResetEvent、ManualResetEvent)和Semaphore以及Mutex都繼承自WaitHandle,所以它們底層的原理是一致的,維護的都是一個系統內核句柄。不過我們仍需簡單的區分下這三類類型。
EventWaitHandle,維護一個由內核產生的布爾類型對象(我們稱之為“阻滯狀態”),如果其值為false,那麼在它上面等待的線程就阻塞。可以調用類型的Set方法將其值設置為true,解除阻塞。EventWaitHandle類型的兩個子類AutoResetEvent和ManualResetEvent,它們的區別並不大,本建議接下來會針對它們闡述如何正確使用信號量。
Semaphore,維護一個由內核產生的整型變量,如果其值為0,則在它上面等待的線程就阻塞,其值大於0,就解除阻塞,同時,每解除阻塞一個線程,其值就減1。
EventWaitHandle和Semaphore提供的都是單應用程序域內的線程同步功能,Mutex則不同,它為我們提供了跨應用程序域阻塞和解除阻塞線程的能力。
1:使用信號機制提供線程同步的一個簡單的例子
使用信號機制提供線程同步的一個簡單的例子如下:
AutoResetEvent autoResetEvent = new AutoResetEvent(false);
private void buttonStartAThread_Click(object sender, EventArgs e)
{
Thread tWork = new Thread(() =>
{
label1.Text = "線程啟動..." + Environment.NewLine;
label1.Text += "開始處理一些實際的工作" + Environment.NewLine;
//省略工作代碼
label1.Text += "我開始等待別的線程給我信號,才願意繼續下去" + Environment.NewLine;
autoResetEvent.WaitOne();
label1.Text += "我繼續做一些工作,然後結束了!";
//省略工作代碼
});
tWork.IsBackground = true;
tWork.Start();
}
private void buttonSet_Click(object sender, EventArgs e)
{
//給在autoResetEvent上等待的線程一個信號
autoResetEvent.Set();
}
這是一個簡單的Winform窗體程序,其中一個按鈕負責開啟一個新的線程,還有一個按鈕負責給剛開啟的那個線程發送信號。現在詳細解釋這裡面發生的事情。
AutoResetEvent autoResetEvent = new AutoResetEvent(false);
這段代碼創建了一個同步類型對象autoResetEvent,它設置自己的默認阻滯狀態是false。這意味著任何在它上面進行等待的線程將會被阻滯。所謂進行等待,就是在線程中應用:
autoResetEvent.WaitOne();
這說明tWork開始在autoResetEvent上等待任何其它地方給它的信號。信號來了,則tWork開始繼續工作,否則就一直等著(即阻滯)。接下來我們看到在主線程中(本例中即UI線程,它相對線程tWork來說,就是一個“另外的線程”):
autoResetEvent.Set();
主線程通過上面這句代碼負責向在autoResetEvent上等待的線程tWork上下文發送信號,即將tWork的阻滯狀態設置為true。tWork接收到這個信號,開始繼續工作。
這個例子相當簡單,但是已經完整說明了信號機制的工作原理。
2:AutoResetEvent和ManualResetEvent的區別
AutoResetEvent和ManualResetEvent有這樣的區別:前者在發送信號完畢後(即調用Set方法),自動將自己的阻滯狀態設置為false,而後者需要進行手動設定。可以通過一個例子來說明這種區別:
AutoResetEvent autoResetEvent = new AutoResetEvent(false);
private void buttonStartAThread_Click(object sender, EventArgs e)
{
StartThread1();
StartThread2();
}
private