版權(quán)聲明:轉(zhuǎn)載時請以超鏈接形式標(biāo)明文章原始出處和作者信息及本聲明
http://xxinside./logs/47523285.html
預(yù)備知識:C#線程同步(1)- 臨界區(qū)&Lock,,C#線程同步(2)- 臨界區(qū)&Monitor,C#線程同步(3)- 互斥量 Mutex
WaitHandle一家
在前一篇我們已經(jīng)提到過Mutex和本篇的主角們直接或間接繼承自WaitHandle:
WaitHandle提供了若干用于同步的方法,。上一篇關(guān)于Mutex的blog中已經(jīng)講到一個WaitOne(),這是一個實例方法,。除此之外,,WaitHandle另有3個用于同步的靜態(tài)方法:
- SignalAndWait(WaitHandle, WaitHandle):以原子操作的形式,向第一個WaitHandle發(fā)出信號并等待第二個,。即喚醒阻塞在第一個WaitHandle上的線程/進(jìn)程,,然后自己等待第二個WaitHandle,且這兩個動作是原子性的,。跟WaitOne()一樣,,這個方法另有兩個重載方法,分別用Int32或者TimeSpan來定義等待超時時間,,以及是否從上下文的同步域中退出,。
- WaitAll(WaitHandle[]):這是用于等待WaitHandle數(shù)組里的所有成員。如果一項工作,,需要等待前面所有人完成才能繼續(xù),,那么這個方法就是一個很好的選擇。仍然有兩個用于控制等待超時的重載方法,,請自行參閱,。
- WaitAny(WaitHandle[]):與WaitAll()不同,WaitAny只要等到數(shù)組中一個成員收到信號就會返回,。如果一項工作,,你只要等最快做完的那個完成就可以開始,那么WaitAny()就是你所需要的,。它同樣有兩個用于控制等待超時的重載,。
線程相關(guān)性(Thread Affinity )
EventWaitHandle和Mutex兩者雖然是派生自同一父類,但有著完全不同的線程相關(guān)性:
- Mutex與Monitor一樣,,是“線程相關(guān)(Thread Affinity)”的,。我們之前已經(jīng)提到過,只有通過Monitor.Enter()/TryEnter()獲得對象鎖的線程才能調(diào)用Pulse()/Wait()/Exit(),;同樣的,,只有獲得Mutex擁有權(quán)的線程才能執(zhí)行ReleaseMutex()方法,否則就會引發(fā)異常,。這就是所謂的線程相關(guān)性,。
- 相反,EventWaitHandle以及它的派生類AutoResetEvent和ManualResetEvent都是線程無關(guān)的,。任何線程都可以發(fā)信號給EventWaitHandle,,以喚醒阻塞在上面的線程。
- 下一篇要提到的Semaphore也是線程無關(guān)的。
Mutex與Event
我們在Mutex一篇中沒有具體提到Mutex是否能發(fā)送信號,,只是簡單說Mutex不太適合有相互消息通知的同步,,它僅有的一些同步方法是來自其父類的靜態(tài)方法。那么現(xiàn)在我們可以仔細(xì)來看看Mutex到底能不能用于關(guān)于Monitor那篇提到的生產(chǎn)者,、消費(fèi)者和糖罐的場景,。
回過頭來仔細(xì)查看Mutex的所有方法,除了一個我們已經(jīng)提到的WaitHandle上的靜態(tài)方法SingnalAndWait(toSingnal, toWaitOn),,我們找不到任何“屬于Mutex自己”的,、用于發(fā)送信號的方法。退而求其次吧,,我們就來看看這個靜態(tài)方法是否可以讓Mutex具有通知的能力,。
如果toSignal是一個Mutex,那么收到“信號”就等效于ReleaseMutex(),。而由于Mutex的線程相關(guān)性,,只有擁有當(dāng)前Mutex的線程才能夠發(fā)送這個信號(ReleaseMutex),否則會引發(fā)異常,。也就是說如果要用這個方法來通知其它線程同步,,Mutex只能自己發(fā)給自己。與之相反,,如果第二個參數(shù)toWaitOn也是個Mutex,,那么這個Mutex不能是自己。因為前篇已經(jīng)講過,,Mutex的擁有者可以多次WaitOne()而不阻塞,,這里也是一樣。所以如果Mutex一定要使用這個方法,,準(zhǔn)確的說是只是成為這個方法的參數(shù),,那只能是WaitHandle.SignalAndWait(它自己,另一個Mutex)。
試想,,如果有人試圖只使用Mutex來進(jìn)行同步通知,。假設(shè)生產(chǎn)者線程通過Mutex上的WaitOne()獲得了mutexA的擁有權(quán),并且在生產(chǎn)完畢后調(diào)用了SingnalAndWait(mutexA,mutexB),,通知由于當(dāng)前mutexA而阻塞的消費(fèi)者線程,,并且將自己阻塞在mutexB上。那么被喚醒的消費(fèi)者線程獲得MutexA的擁有權(quán)吃掉糖后,,也只能調(diào)用SingnalAndWait(mutexA,mutexB)釋放它獲得的mutexA且阻塞于MutexB,。問題來了,此時的生產(chǎn)者是阻塞在mutexB上……也許,,我們可以設(shè)計一段“精巧”的代碼,,讓生產(chǎn)者和消費(fèi)者一會兒阻塞在mutexA,,一會兒阻塞在mutexB上……我不想花費(fèi)這個力氣去想了,你可以試試看:),。不管有沒有這樣的可能,,Mutex很明顯就不適用于通知的場景。
EventWaitHandle的獨(dú)門秘笈
正因為Mutex沒有很好地繼承父輩的衣缽,,EventWaitHandle以及它的兒子/女兒們便來到了這個世界上。
EventWaitHandle,、AutoResetEvent,、ManualResetEvent名字里都有一個“Event”,不過這跟.net的本身的事件機(jī)制完全沒有關(guān)系,,它不涉及任何委托或事件處理程序,。相對于我們之前碰到的Monitor和Mutex需要線程去爭奪“鎖”而言,我們可以把它們理解為一些需要線程等待的“事件”,。線程通過等待這些事件的“發(fā)生”,,把自己阻塞起來。一旦“事件”完成,,被阻塞的線程在收到信號后就可以繼續(xù)工作,。
為了配合WaitHandle上的3個靜態(tài)方法SingnalAndWait()/WailAny()/WaitAll(),EventWaitHandle提供了自己獨(dú)有的,,使“Event”完成和重新開始的方法:
- bool:Set():英文版MSDN:Sets the state of the event to signaled, allowing one or more waiting threads to proceed,;中文版MSDN:將事件狀態(tài)設(shè)置為終止?fàn)顟B(tài),允許一個或多個等待線程繼續(xù),。初看“signaled”和“終止”似乎并不對應(yīng),,細(xì)想起來這兩者的說法其實也不矛盾。事件如果在進(jìn)行中,,當(dāng)然就沒有“終止”,,那么其它線程就需要等待;一旦事件完成,,那么事件就“終止”了,,于是我們發(fā)送信號喚醒等待的線程,所以“信號已發(fā)送”狀態(tài)也是合理的,。兩個小細(xì)節(jié):
- 無論中文還是英文版,,都提到這個方法都是可以讓“一個”或“多個”等待線程“繼續(xù)/Proceed”(注意不是“喚醒”)。所以這個方法在“喚醒”這個動作上是類似于Monitor.Pulse()和Monitor.PulseAll()的,。至于什么時候類似Pulse(),,又在什么時候類似PulseAll(),往下看,。
- 這個方法有bool型的返回值:如果該操作成功,,則為true,;否則,為false,。不過MSDN并沒有告訴我們,,什么時候執(zhí)行會失敗,你只有找個微軟MVP問問了,。
- bool:Reset():Sets the state of the event to nonsignaled, causing threads to block. 將事件狀態(tài)設(shè)置為非終止?fàn)顟B(tài),,導(dǎo)致線程阻止。 同樣,,我們需要明白“nonsignaled”和“非終止”是一回事情,。還同樣的是,仍然有個無厘頭的返回值,。Reset()的作用,,相當(dāng)于讓事件重新開始處于“進(jìn)行中”,那么此后所有WaitOne()/WaitAll()/WaitAny()/SignalAndWait()這個事件的線程都會再次被擋在門外,。
來看看EventWaitHandle眾多構(gòu)造函數(shù)中最簡單的一個:
- EventWaitHandle(Boolean initialState, EventResetMode mode):初始化EventWaitHandle類的新實例,,并指定等待句柄最初是否處于終止?fàn)顟B(tài),以及它是自動重置還是手動重置,。大多數(shù)時候我們會在第一個參數(shù)里使用false,,這樣新實例會缺省為“非終止”狀態(tài)。第二個參數(shù)EventResetMode是一個枚舉,,一共兩個值:
- EventResetMode.AutoReset:當(dāng)Set()被調(diào)用當(dāng)前EventWaitHandle轉(zhuǎn)入終止?fàn)顟B(tài)時,,若有線程阻塞在當(dāng)前EventWaitHandle上,那么在釋放一個線程后EventWaitHandle就會自動重置(相當(dāng)于自動調(diào)用Reset())再次轉(zhuǎn)入非終止?fàn)顟B(tài),,剩余的原來阻塞的線程(如果有的話)還會繼續(xù)阻塞,。如果調(diào)用Set()后本沒有線程阻塞,那么EventWaitHandle將保持“終止”狀態(tài)直到一個線程嘗試等待該事件,,這個該線程不會被阻塞,,此后EventWaitHandle才會自動重置并阻塞那之后的所有線程。
- EventResetMode.ManualReset:當(dāng)終止時,,EventWaitHandle 釋放所有等待的線程,,并在手動重置前,,即Reset()被調(diào)用前,,一直保持終止?fàn)顟B(tài),。
好了,,現(xiàn)在我們可以清楚的知道Set()在什么時候分別類似于Monitor.Pulse()/PulseAll()了:
- 當(dāng)EventWaitHandle工作在AutoReset模式下,就喚醒功能而言,,Set()與Monitor.Pulse()類似,。此時,,Set()只能喚醒眾多(如果有多個的話)被阻塞線程中的一個,。但兩者仍有些差別:
- Set()的作用不僅僅是“喚醒”而是“釋放”,,可以讓線程繼續(xù)工作(proceed),;相反,Pulse()喚醒的線程只是重新進(jìn)入Running狀態(tài),,參與對象鎖的爭奪,,誰都不能保證它一定會獲得對象鎖。
- Pulse()的已被調(diào)用的狀態(tài)不會被維護(hù),。因此,,如果在沒有等待線程時調(diào)用Pulse(),那么下一個調(diào)用Monitor.Wait()的線程仍然會被阻塞,,就像Pulse() 沒有被被調(diào)用過,。也就是說Monitor.Pulse()只在調(diào)用當(dāng)時發(fā)揮作用,并不象Set()的作用會持續(xù)到下一個WaitXXX(),。
- 在一個工作在ManualReset模式下的EventWaitHandle的Set()方法被調(diào)用時,它所起到的喚醒作用與Monitor.PulseAll()類似,,所有被阻塞的線程都會收到信號被喚醒,。而兩者的差別與上面完全相同。
來看看EventWaitHandle的其它構(gòu)造函數(shù):
- EventWaitHandle(Boolean initialState, EventResetMode mode, String name):頭兩個參數(shù)我們已經(jīng)看過,,第三個參數(shù)name用于在系統(tǒng)范圍內(nèi)指定同步事件的名稱,。是的,正如我們在Mutex一篇中提到的,,由于父類WaitHandle是具有跨進(jìn)程域的能力的,,因此跟Mutex一樣,我們可以創(chuàng)建一個全局的EventWaitHandle,,讓后將它用于進(jìn)程間的通知,。注意,name仍然是大小寫敏感的,,仍然有命名前綴的問題跟,,你可以參照這里。當(dāng)name為null或空字符串時,,這等效于創(chuàng)建一個局部的未命名的EventWaitHandle,。仍然同樣的還有,可能會因為已經(jīng)系統(tǒng)中已經(jīng)有同名的EventWaitHandle而僅僅返回一個實例表示同名的EventWaitHandle,。所以最后仍舊同樣地,,如果你需要知道這個EventWaitHandle是否由你最先創(chuàng)建,你需要使用以下兩個構(gòu)造函數(shù)之一,。
- EventWaitHandle(Boolean initialState, EventResetMode mode, String name, out Boolean createdNew):createdNew用于表明是否成功創(chuàng)建了EventWaitHandle,,true表明成功,false表明已經(jīng)存在同名的事件,。
- EventWaitHandle(Boolean initialState, EventResetMode mode, String name, out Boolean createdNew, EventWaitHandleSecurity):關(guān)于安全的問題,,直接查看這個構(gòu)造函數(shù)上的例子吧,。全局MutexEventWaitHandle的安全問題應(yīng)該相對Mutex更需要注意,因為有可能黑客程序用相同的事件名對你的線程發(fā)送信號或者進(jìn)行組織,,那樣可能會嚴(yán)重危害你的業(yè)務(wù)邏輯,。
好啦,都差不多了,,可以寫一個例子試試了,。讓我們回到Monitor一篇中提到的生產(chǎn)者和消費(fèi)者場景,讓我們看看EventWaitHandle能不能完成它兄弟Mutex沒有能完成的事業(yè),。不過,,即便有強(qiáng)大通信能力的EventWaitHandle出馬,也避免不要使用lock/monitor或是Mutex,。原因很簡單,,糖罐是一個互斥資源,必須被互斥地訪問,。而EventWaitHanldle跟Mutex相反,,能通信了但卻完全失去了臨界區(qū)的能力。所以,,這個例子其實并不太適合展示EventWaitHandle的通信機(jī)制,,我只是為了想用同樣的例子來比較這些同步機(jī)制間的差異。
EventWaitHandle雖然還必須借助lock/Monitor/Mutex來實現(xiàn)這個例子(僅僅是臨界區(qū)部分),,但是它終究有強(qiáng)于Monitor的通信能力,,所以讓我們來擴(kuò)展一下這個例子:現(xiàn)在有一個生產(chǎn)者,有多個消費(fèi)者,。
- 我們讓消費(fèi)者在沒有糖吃或吃完一塊糖后阻塞在一個工作在ManualReset模式下的EventWaitHandle,,生產(chǎn)者在生產(chǎn)完畢后就通過這個事件喚醒所有消費(fèi)者吃糖。由于我們使用了lock的關(guān)系,,雖然所有消費(fèi)者都被喚醒,,但是他們還是因為爭奪糖罐的關(guān)系只有一個能進(jìn)入臨界區(qū)吃糖。不過此時阻塞的原因并不是因為我們的通知時間,,而是臨界區(qū)的問題,。
- 每個消費(fèi)者有一條專線,即一個工作在AutoRest模式下的EventWaitHandle,,用于在吃完糖后通知生產(chǎn)者,。而生產(chǎn)者用WaitAny()來等待消費(fèi)者吃糖時間的發(fā)生,只要有任一消費(fèi)者吃完糖,,那么生產(chǎn)者就試圖爭奪對糖罐的擁有權(quán),,把糖罐塞滿(一人一顆的標(biāo)準(zhǔn))。消費(fèi)者這里使用了WaitAndSignal給生產(chǎn)者發(fā)消息,,并等待生產(chǎn)者進(jìn)入臨界區(qū)生產(chǎn)糖后通知他們,。在這樣的設(shè)計邏輯下,,可能糖罐中的糖還沒有全部吃完生產(chǎn)者就有機(jī)會再次把糖罐裝滿。當(dāng)然,,你也可以使用了WaitAll()來等待所有消費(fèi)者吃完再進(jìn)行生產(chǎn),。
using System;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading;
class WaitEventHandleSample:IDisposable
{
private volatile bool _shouldStop = false; //用于控制線程正常結(jié)束的標(biāo)志
private const int _numberOfConsumer = 5; //消費(fèi)者的數(shù)目
//容器,一個只能容納一塊糖的糖盒子,。PS:現(xiàn)在MS已經(jīng)不推薦使用ArrayList,,
//支持泛型的List才是應(yīng)該在程序中使用的,我這里偷懶,,不想再去寫一個Candy類了,。
private ArrayList _candyBox = null;
private EventWaitHandle _EvntWtHndlProduced = null; //生產(chǎn)完成的事件,ManualReset,,用于通知所有消費(fèi)者生產(chǎn)完成
private EventWaitHandle[] _EvntWtHndlConsumed = null; //消費(fèi)完成的事件,,AutoReset,每一個消費(fèi)線程對應(yīng)一個事件,,用于通知生產(chǎn)者有消費(fèi)動作完成
/// <summary>
/// 用于結(jié)束Produce()和Consume()在輔助線程中的執(zhí)行
/// </summary>
public void StopThread()
{
_shouldStop = true;
//叫醒阻塞中的消費(fèi)者,,讓他們看到線程結(jié)束標(biāo)志
if (_EvntWtHndlProduced != null)
{
_EvntWtHndlProduced.Set();
};
//叫醒阻塞中的生產(chǎn)者,讓他看到線程結(jié)束標(biāo)志
if (_EvntWtHndlConsumed != null)
{
for (int i = 0; i < _numberOfConsumer; i++)
{
if (_EvntWtHndlConsumed[i] != null)
{
_EvntWtHndlConsumed[i].Set();
};
}
}
}
/// <summary>
/// 生產(chǎn)者的方法
/// </summary>
public void Produce()
{
if (_candyBox == null)
{
Console.WriteLine("生產(chǎn)者:糖罐在哪里,?!");
}
else if (_EvntWtHndlConsumed == null)
{
Console.WriteLine("生產(chǎn)者:消費(fèi)者們在哪里,?,!");
}
else if (_EvntWtHndlProduced == null) //這個事件用于喚醒所有消費(fèi)者,,因此象個喇叭
{
Console.WriteLine("生產(chǎn)者:喇叭壞啦,,沒辦法通知消費(fèi)者!");
}
else
{
//逐一檢查消費(fèi)者是否到位
for (int i = 0; i < _numberOfConsumer; ++i)
{
if (_EvntWtHndlConsumed[i] == null)
{
Console.WriteLine("生產(chǎn)者:消費(fèi)者{0}在哪里,?,!", i);
return;
}
else
{
//什么也不做
};
};
int numberOfSugarProduced = 0; //本次一共生產(chǎn)了多少顆糖
while (!_shouldStop)
{
lock (_candyBox)
{
if (_candyBox.Count < _numberOfConsumer)
{
numberOfSugarProduced = 0;
while (_candyBox.Count < _numberOfConsumer) //一共有多少個消費(fèi)者就生產(chǎn)多少塊糖
{
//生產(chǎn)一塊糖
_candyBox.Add("A Candy");
++numberOfSugarProduced;
};
Console.WriteLine("生產(chǎn)者:這次生產(chǎn)了{(lán)0}塊糖,,罐里現(xiàn)在一共有{1}塊糖!", numberOfSugarProduced, _candyBox.Count);
Console.WriteLine("生產(chǎn)者:趕快來吃?。?);
}
else //容器是滿的
{
Console.WriteLine("生產(chǎn)者:糖罐是滿的,!");
};
};
//通知消費(fèi)者生產(chǎn)已完成
_EvntWtHndlProduced.Set();
//只要有消費(fèi)者吃完糖,,就開始生產(chǎn)
EventWaitHandle.WaitAny(_EvntWtHndlConsumed);
Thread.Sleep(2000);
};
Console.WriteLine("生產(chǎn)者:下班啦,!");
}
}
/// <summary>
/// 消費(fèi)者的方法
/// </summary>
/// <param name="consumerIndex">消費(fèi)者序號,,用于表明使用哪個_EvntWtHndlConsumed成員</param>
public void Consume(object consumerIndex)
{
int index = (int)consumerIndex;
if (_candyBox == null)
{
Console.WriteLine("消費(fèi)者{0}:糖罐在哪里,?,!",index);
}
else if (_EvntWtHndlProduced == null)
{
Console.WriteLine("消費(fèi)者{0}:生產(chǎn)者在哪里?,!",index);
}
else if (_EvntWtHndlConsumed == null || _EvntWtHndlConsumed[index] == null)
{
Console.WriteLine("消費(fèi)者{0}:電話壞啦,,沒辦法通知生產(chǎn)者!", index); //由于每個消費(fèi)者都有一個專屬事件通知生產(chǎn)者,,因此相當(dāng)于電話
}
else
{
while (!_shouldStop || _candyBox.Count > 0) //即便看到結(jié)束標(biāo)致也應(yīng)該把容器中的所有資源處理完畢再退出,,否則容器中的資源可能就此丟失。需要指出_candybox.Count是有可能讀到臟數(shù)據(jù)的
{
lock (_candyBox)
{
if (_candyBox.Count > 0)
{
if (!_shouldStop)
{
_candyBox.RemoveAt(0);
Console.WriteLine("消費(fèi)者{0}:吃了1顆糖,,還剩{1}顆?。?, index, _candyBox.Count);
Console.WriteLine("消費(fèi)者{0}:趕快生產(chǎn)??!",index);
}
else
{
Console.WriteLine("消費(fèi)者{0}:我來把剩下的糖都吃了!",index);
while (_candyBox.Count > 0)
{
_candyBox.RemoveAt(0);
Console.WriteLine("消費(fèi)者{0}:吃了1顆糖,,還剩{1}顆?。?, index, _candyBox.Count);
}
break;
}
}
else
{
Console.WriteLine("消費(fèi)者{0}:糖罐是空的,!",index);
Console.WriteLine("消費(fèi)者{0}:趕快生產(chǎn)?。?,index);
}
}
WaitHandle.SignalAndWait(_EvntWtHndlConsumed[index], _EvntWtHndlProduced);
Thread.Sleep((index+1)*1500);
}
}
Console.WriteLine("消費(fèi)者{0}:都吃光啦,,下次再吃,!",index);
}
/// <summary>
/// 初始化所需的各EventWaitHandle和糖罐等
/// </summary>
public void Initialize()
{
if (_candyBox == null)
{
_candyBox = new ArrayList(_numberOfConsumer); //按有多少消費(fèi)者最多生產(chǎn)多少糖的標(biāo)準(zhǔn)初始化糖罐大小
}
else
{
//什么也不做
}
if (_EvntWtHndlProduced == null)
{
_EvntWtHndlProduced = new EventWaitHandle(false, EventResetMode.ManualReset);
}
else
{
//什么也不做
}
if (_EvntWtHndlConsumed == null)
{
_EvntWtHndlConsumed = new EventWaitHandle[_numberOfConsumer];
for (int i = 0; i < _numberOfConsumer; ++i)
{
_EvntWtHndlConsumed[i] = new EventWaitHandle(false, EventResetMode.AutoReset);
}
}
else
{
//什么也不做
}
}
static void Main(string[] args)
{
WaitEventHandleSample ss = new WaitEventHandleSample();
try
{
ss.Initialize();
//Start threads.
Console.WriteLine("開始啟動線程,輸入回車終止生產(chǎn)者和消費(fèi)者的工作……\r\n******************************************");
Thread thdProduce = new Thread(new ThreadStart(ss.Produce));
thdProduce.Start();
Thread[] thdConsume = new Thread[_numberOfConsumer];
for (int i = 0; i < _numberOfConsumer; ++i)
{
thdConsume[i] = new Thread(new ParameterizedThreadStart(ss.Consume));
thdConsume[i].Start(i);
}
Console.ReadLine(); //通過IO阻塞主線程,,等待輔助線程演示直到收到一個回車
ss.StopThread(); //正常且優(yōu)雅的結(jié)束生產(chǎn)者和消費(fèi)者線程
thdProduce.Join();
for (int i = 0; i < _numberOfConsumer; ++i)
{
thdConsume[i].Join();
}
Console.WriteLine("******************************************\r\n輸入回車結(jié)束,!");
Console.ReadLine();
}
finally
{
ss.Dispose();
ss = null;
};
}
#region IDisposable Members
public void Dispose()
{
if (_candyBox != null)
{
_candyBox.Clear();
_candyBox = null;
}
else
{
//什么也不做
}
if (_EvntWtHndlProduced != null)
{
_EvntWtHndlProduced.Set();
_EvntWtHndlProduced.Close();
_EvntWtHndlProduced = null;
}
else
{
//什么也不做
}
if (_EvntWtHndlConsumed != null)
{
for (int i = 0; i < _numberOfConsumer; ++i)
{
if (_EvntWtHndlConsumed[i] != null)
{
_EvntWtHndlConsumed[i].Set();
_EvntWtHndlConsumed[i].Close();
_EvntWtHndlConsumed[i] = null;
};
}
_EvntWtHndlConsumed = null;
}
else
{
//什么也不做
};
}
#endregion
}
Produce()和Consum()中加入的Sleep代碼僅僅是為了讓線程更為隨機(jī)的被調(diào)度,這樣我們可以更容易觀察到線程亂序執(zhí)行的情況,。另外,,如果是一個需要跨進(jìn)程同步的程序,那么你也可以用Mutext替換lock實現(xiàn)臨界區(qū),。下面是某次執(zhí)行的輸出情況,,你的結(jié)果當(dāng)然會跟它不同(空行位置是我輸入回車終止線程的時機(jī)):
開始啟動線程,輸入回車終止生產(chǎn)者和消費(fèi)者的工作……
******************************************
生產(chǎn)者:這次生產(chǎn)了5塊糖,,罐里現(xiàn)在一共有5塊糖,!
生產(chǎn)者:趕快來吃!!
消費(fèi)者0:吃了1顆糖,,還剩4顆?。?br>消費(fèi)者0:趕快生產(chǎn)??!
消費(fèi)者1:吃了1顆糖,還剩3顆??!
消費(fèi)者1:趕快生產(chǎn)!,!
消費(fèi)者2:吃了1顆糖,,還剩2顆!,!
消費(fèi)者2:趕快生產(chǎn)?。?br>消費(fèi)者3:吃了1顆糖,,還剩1顆?。?br>消費(fèi)者3:趕快生產(chǎn)??!
消費(fèi)者4:吃了1顆糖,還剩0顆??!
消費(fèi)者4:趕快生產(chǎn)!,!
消費(fèi)者0:糖罐是空的,!
消費(fèi)者0:趕快生產(chǎn)??!
生產(chǎn)者:這次生產(chǎn)了5塊糖,罐里現(xiàn)在一共有5塊糖,!
生產(chǎn)者:趕快來吃?。?br>消費(fèi)者1:吃了1顆糖,,還剩4顆?。?br>消費(fèi)者1:趕快生產(chǎn)??!
消費(fèi)者0:吃了1顆糖,還剩3顆!,!
消費(fèi)者0:趕快生產(chǎn)?。?br>生產(chǎn)者:這次生產(chǎn)了2塊糖,,罐里現(xiàn)在一共有5塊糖,!
生產(chǎn)者:趕快來吃!,!
消費(fèi)者0:吃了1顆糖,,還剩4顆!,!
消費(fèi)者0:趕快生產(chǎn)?。?br>消費(fèi)者2:吃了1顆糖,,還剩3顆?。?br>消費(fèi)者2:趕快生產(chǎn)??!
消費(fèi)者1:吃了1顆糖,還剩2顆??!
消費(fèi)者1:趕快生產(chǎn)!,!
生產(chǎn)者:這次生產(chǎn)了3塊糖,,罐里現(xiàn)在一共有5塊糖!
生產(chǎn)者:趕快來吃??!
消費(fèi)者0:吃了1顆糖,還剩4顆??!
消費(fèi)者0:趕快生產(chǎn)!,!
消費(fèi)者3:吃了1顆糖,,還剩3顆!,!
消費(fèi)者3:趕快生產(chǎn)?。?br>消費(fèi)者4:吃了1顆糖,,還剩2顆??!
消費(fèi)者4:趕快生產(chǎn)!,!
消費(fèi)者0:吃了1顆糖,,還剩1顆!,!
消費(fèi)者0:趕快生產(chǎn)?。?/em>
生產(chǎn)者:下班啦,!
消費(fèi)者1:我來把剩下的糖都吃了,!
消費(fèi)者1:吃了1顆糖,還剩0顆??!
消費(fèi)者1:都吃光啦,下次再吃,!
消費(fèi)者0:都吃光啦,,下次再吃!
消費(fèi)者2:都吃光啦,,下次再吃,!
消費(fèi)者3:都吃光啦,下次再吃,!
消費(fèi)者4:都吃光啦,,下次再吃!
******************************************
輸入回車結(jié)束,!
AutoResetEvent & ManuResetEvent
到此為止我們還沒有提到過EventWaitHandle的這兩個兒子,,不過這就是一兩句話的事:
- AutoResetEvent在功能上等效于用EventResetMode.AutoReset 創(chuàng)建的未命名的 EventWaitHandle。
- ManualResetEvent在功能上等效于用EventResetMode.ManualReset 創(chuàng)建的未命名的 EventWaitHandle,。
好了,,講這么都就夠了,這兩個子類無非是為了方便使用而存在的,。不過請記得這兩個子類永遠(yuǎn)是局部/Local的,,并不能象它們的父類一樣用于進(jìn)程間的通信。
還是給出一個簡單的例子,,這個例子只跟通知有關(guān),,不再涉及臨界資源,。假設(shè)一個跑步比賽的場景,,我們用一個ManualResetEvent表示比賽,然后為每個運(yùn)動員配備一個AutoResetEvent用于通知到起跑線或者是達(dá)終點(diǎn),。首先運(yùn)動員需要到起跑線上就位,,這個過程我們讓運(yùn)動員到達(dá)起跑線后調(diào)用AutoResetEvent上的Reset()發(fā)出信號,同時使用ManualResetEvent上的WaitOne()阻塞自己準(zhǔn)備起跑。另一方面,,我們在比賽線程上先用WaitHandle.WaitAll(AutoResetEvent[])等待所有運(yùn)動員到位,。WaitAll()完成后,使用ManualResetEvent上的Reset()發(fā)令開始比賽,,再使用WaitHandle.WaitAny(AutoResetEvent[])等待第一個運(yùn)動員沖線,。而每個運(yùn)動員到終點(diǎn)后會再次調(diào)用AutoResetEvent.Reset()表示到達(dá)。
using System;
using System.Threading;
using System.Linq;
using System.Text;
class Runner : IDisposable
{
//用于讓所有運(yùn)動員到達(dá)起跑線準(zhǔn)備起跑
private ManualResetEvent _mnlRstEvntStartLine = null;
//用于運(yùn)動員到達(dá)終點(diǎn)時發(fā)出信號
private static AutoResetEvent[] _mnlRstEvntRunner = null;
private const int _numberOfRunner = 8;
private Random _rnd = new Random();
/// <summary>
/// 構(gòu)造函數(shù)
/// </summary>
public Runner()
{
_mnlRstEvntStartLine = new ManualResetEvent(false);
_mnlRstEvntRunner = new AutoResetEvent[_numberOfRunner];
//請運(yùn)動員就位
for (int i = 0; i < _numberOfRunner; ++i)
{
_mnlRstEvntRunner[i] = new AutoResetEvent(false);
}
}
/// <summary>
/// 運(yùn)動員方法
/// </summary>
/// <param name="id">運(yùn)動員序號</param>
public void Run(object id)
{
int index = (int)id;
//等待信號準(zhǔn)備起跑
Console.WriteLine("{0}號運(yùn)動員就位,。", index);
_mnlRstEvntRunner[index].Set();
//等待發(fā)令
_mnlRstEvntStartLine.WaitOne();
//隨機(jī)睡眠,,表示不同運(yùn)動員跑的快慢
Thread.Sleep(_rnd.Next(2000));
Console.WriteLine("{0}號運(yùn)動員到達(dá)終點(diǎn)!", index);
_mnlRstEvntRunner[index].Set();
}
/// <summary>
/// 比賽開始
/// </summary>
public void Start()
{
Thread[] runners = new Thread[_numberOfRunner];
//請運(yùn)動員就位
for (int i = 0; i < _numberOfRunner; ++i)
{
runners[i] = new Thread(Run);
runners[i].Start(i);
}
//等待所有運(yùn)動員就位
WaitHandle.WaitAll(_mnlRstEvntRunner);
//發(fā)令起跑
Console.WriteLine("***********************起跑?。,。?************************");
_mnlRstEvntStartLine.Set();
//看看誰先到達(dá)終點(diǎn)
int index = WaitHandle.WaitAny(_mnlRstEvntRunner);
//等待所有運(yùn)動員到達(dá)終點(diǎn)
//請運(yùn)動員就位
for (int i = 0; i < _numberOfRunner; ++i)
{
runners[i].Join();
}
Console.WriteLine("**********************************************************");
Console.WriteLine("{0}號運(yùn)動員奪得冠軍,!", index);
Console.WriteLine("***********************比賽結(jié)束***************************");
}
static void Main()
{
Runner ss = new Runner();
try
{
ss.Start();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
ss.Dispose();
ss = null;
Console.WriteLine("輸入回車結(jié)束");
Console.ReadLine();
}
}
#region IDisposable Members
public void Dispose()
{
if (_mnlRstEvntStartLine != null)
{
_mnlRstEvntStartLine.Set();
_mnlRstEvntStartLine.Close();
}
else
{
//do nothing
}
if (_mnlRstEvntRunner != null)
{
for (int i = 0; i < _numberOfRunner; ++i)
{
if (_mnlRstEvntRunner[i] != null)
{
_mnlRstEvntRunner[i].Set();
_mnlRstEvntRunner[i].Close();
_mnlRstEvntRunner[i] = null;
}
else
{
//do nothing
}
}
_mnlRstEvntRunner = null;
}
}
#endregion
}
可能的執(zhí)行結(jié)果:
0號運(yùn)動員就位,。
1號運(yùn)動員就位。
2號運(yùn)動員就位,。
3號運(yùn)動員就位,。
4號運(yùn)動員就位。
5號運(yùn)動員就位,。
6號運(yùn)動員就位,。
7號運(yùn)動員就位。
***********************起跑?。,。?************************
3號運(yùn)動員到達(dá)終點(diǎn),!
1號運(yùn)動員到達(dá)終點(diǎn),!
0號運(yùn)動員到達(dá)終點(diǎn)!
4號運(yùn)動員到達(dá)終點(diǎn),!
2號運(yùn)動員到達(dá)終點(diǎn),!
5號運(yùn)動員到達(dá)終點(diǎn)!
6號運(yùn)動員到達(dá)終點(diǎn),!
7號運(yùn)動員到達(dá)終點(diǎn),!
**********************************************************
3號運(yùn)動員奪得冠軍!
***********************比賽結(jié)束***************************
題外話:派生總是優(yōu)雅的嗎,?
在WaitHandle家族這個繼承關(guān)系里,,我實在忍不住要說“丑陋”兩個字。Mutex以及下篇將要講到的信號量Semaphore,,實在是太委屈地接受了來自WaitHandle上不相關(guān)的靜態(tài)方法,。WaitAll(),,WaitAny(),SignalAndWait()完完全全就是為EventWaitHandle這一族定制的,。繼承本來想體現(xiàn)的多態(tài)性,,也僅僅是體現(xiàn)在這幾個方法的參數(shù)是WaitHandle上,不過有誰會真的在這幾個方法上使用Mutex或者Semaphore實例呢,?也許Mutex和Semaphore是WaitHandle“抱養(yǎng)”的吧,,否則它怎么這么偏心?:)
Mutex與EventWaitHandle完全是站在同步的兩個方向:Mutex是“鎖”可以實現(xiàn)互斥訪問但幾乎不具有通信能力,;而EventWaitHandle有強(qiáng)大的通信能力,,但卻不能實現(xiàn)對資源的互斥訪問。從一個父類,,派生出兩個有如此大差異的子類實在不知道是為何,。從這種意義上來講,似乎Monitor比較“全面”,,兩邊都能做一點(diǎn),。
在基礎(chǔ)類庫里出現(xiàn)這樣的狀況,似乎確實無法對此表示信服(這可能是有些Java程序員鄙視.Net一脈的原因之一吧,,Java在語言規(guī)范和OO理論上的優(yōu)雅的確有些讓人著迷:)),。不過,我們還是要體諒一下MS,。它的產(chǎn)品線是那么龐大,,產(chǎn)品生命周期是那么持久,你不可能期望Windows API剛出現(xiàn)的時候就能夠為.Net未來的優(yōu)雅考慮,。一代代的更替中,,他們總需要面對之前實現(xiàn)的一些限制。畢竟這幾個類的根源是比較直接地對Win32 API地封裝,。
上一篇: C#線程同步(3)- 互斥量 Mutex 下一篇:C#線程同步(5)- 信號量 Semaphore