由于Monitor.Wait的暫時(shí)放棄和Monitor.Pulse的開門機(jī)制,我們可以用Monitor來實(shí)現(xiàn)更豐富的同步機(jī)制,比如一個(gè)事件機(jī)(ManualResetEvent):
- C# code
class MyManualEvent
{
private object lockObj = new object();
private bool hasSet = false;
public void Set()
{
lock (lockObj)
{
hasSet = true;
Monitor.PulseAll(lockObj);
}
}
public void WaitOne()
{
lock (lockObj)
{
while (!hasSet)
{
Monitor.Wait(lockObj);
}
}
}
}
class Program
{
static MyManualEvent myManualEvent = new MyManualEvent();
static void Main(string[] args)
{
ThreadPool.QueueUserWorkItem(WorkerThread, "A");
ThreadPool.QueueUserWorkItem(WorkerThread, "B");
Console.WriteLine("Press enter to signal the green light");
Console.ReadLine();
myManualEvent.Set();
ThreadPool.QueueUserWorkItem(WorkerThread, "C");
Console.ReadLine();
}
static void WorkerThread(object state)
{
myManualEvent.WaitOne();
Console.WriteLine("Thread {0} got the green light...", state);
}
}
我們看到了該玩具M(jìn)yManualEvent實(shí)現(xiàn)了類庫中的ManulaResetEvent的功能,但卻更加的輕便 -
類庫的ManulaResetEvent使用了操作系統(tǒng)內(nèi)核事件機(jī)制,負(fù)擔(dān)比較大(不算競態(tài)時(shí)間,ManulaResetEvent是微秒級,而lock是幾十納秒級)。
例子的WaitOne中先在lock的保護(hù)下判斷是否信號綠燈,如果不是則進(jìn)入等待。因此可以有多個(gè)線程(比如例子中的AB)在等待隊(duì)列中排隊(duì)。
當(dāng)調(diào)用Set的時(shí)候,在lock的保護(hù)下信號轉(zhuǎn)綠,并使用PulseAll開門放狗,將所有排在等待隊(duì)列中的線程放入就緒隊(duì)列,A或B(比如A)于是可以重新獲得同步對象,從Monitor.Wait退出,并隨即退出lock區(qū)塊,WaitOne返回。隨后B或A(比如B)重復(fù)相同故事,并從WaitOne返回。
線程C在myManualEvent.Set()后才執(zhí)行,它在WaitOne中確信信號燈早已轉(zhuǎn)綠,于是可以立刻返回并得以執(zhí)行隨后的命令。
該玩具M(jìn)yManualEvent可以用在需要等待初始化的場合,比如多個(gè)工作線程都必須等到初始化完成后,接到OK信號后才能開工。該玩具M(jìn)yManualEvent比起ManulaResetEvent有很多局限,比如不能跨進(jìn)程使用,但它演示了通過基本的Monitor命令組合,達(dá)到事件機(jī)的作用。
現(xiàn)在是回答朋友們的疑問的時(shí)候了:
Q: Lock關(guān)鍵字不是有獲取鎖、釋放鎖的功能... 為什么還需要執(zhí)行Pulse?
A:
因?yàn)閃ait和Pulse另有用途。
Q: 用lock
就不要用monitor了(?)
A:
lock只是Monitor.Enter和Monitor.Exit,用Monitor的方法,不僅能用Wait,還可以用帶超時(shí)的Monitor.Enter重載。
Q: Monitor.Wait完全沒必要 (?)
A:
Wait和Pulse另有用途。
Q:
什么Pulse和Wait方法必須從同步的代碼塊內(nèi)調(diào)用?
A:
因?yàn)閃ait的本意就是“[暫時(shí)]釋放對象上的鎖并阻止當(dāng)前線程,直到它重新獲取該鎖”,沒有獲得就談不到釋放。
我們知道lock實(shí)際上一個(gè)語法糖糖,C#編譯器實(shí)際上把他展開為Monitor.Enter和Monitor.Exit,即:
- C# code
lock(lockObj)
{
//...
}
////相當(dāng)于(.Net4以前):
Monitor.Enter(lockObj);
try
{
//...
}
finally
{
Monitor.Exit(lockObj);
}
但是,這種實(shí)現(xiàn)邏輯至少理論上有一個(gè)錯(cuò)誤:當(dāng)Monitor.Enter(lockObj);剛剛完成,還沒有進(jìn)入try區(qū)的時(shí)候,有可能從其他線程發(fā)出了Thread.Abort等命令,使得該線程沒有機(jī)會(huì)進(jìn)入try...finally。也就是說lockObj沒有辦法得到釋放,有可能造成程序死鎖。這也是Thread.Abort一般被認(rèn)為是邪惡的原因之一。
DotNet4開始,增加了Monitor.Enter(object,ref
bool)重載。而C#編譯器會(huì)把lock展開為更安全的Monitor.Enter(object,ref bool)和Monitor.Exit:
- C# code
lock(lockObj)
{
//...
}
////相當(dāng)于(DotNet 4):
bool lockTaken = false;
try
{
Monitor.Enter(lockObj,ref lockTaken);
//
}
finally
{
if (lockTaken) Monitor.Exit(lockObj);
}
現(xiàn)在Monitor.TryEnter在try的保護(hù)下,“加鎖”成功意味著“放鎖”將得到finally的保護(hù)。
注釋和引用:Monitor.Wait 方法
http://msdn.microsoft.com/zh-cn/library/79fkfcw1.aspx
Monitor.TryEnter
方法http://msdn.microsoft.com/zh-cn/library/dd289679.aspx
請問,多線程Monitor類http://topic.csdn.net/u/20111206/15/744c70de-49dc-4694-a09e-180438d7f8f0.html
請問,這個(gè)關(guān)于多線程的代碼不懂http://topic.csdn.net/u/20111208/23/64671dd4-7fdc-4d76-b3b9-1fd18087e6e0.html