Monitor的code以下,很是簡單:spa
public static class Monitor { public static extern void Enter(Object obj); public static void Enter(Object obj, ref bool lockTaken) { if (lockTaken) ThrowLockTakenException(); ReliableEnter(obj, ref lockTaken); Contract.Assert(lockTaken); } private static extern void ReliableEnter(Object obj, ref bool lockTaken); public static void TryEnter(Object obj, ref bool lockTaken) { if (lockTaken) ThrowLockTakenException(); ReliableEnterTimeout(obj, 0, ref lockTaken); } private static extern void ReliableEnterTimeout(Object obj, int timeout, ref bool lockTaken); public static extern void Exit(Object obj); public static bool Wait(Object obj, int millisecondsTimeout, bool exitContext) { if (obj == null) throw (new ArgumentNullException("obj")); return ObjWait(exitContext, millisecondsTimeout, obj); } private static extern bool ObjWait(bool exitContext, int millisecondsTimeout, Object obj); public static void Pulse(Object obj) { if (obj == null) { throw new ArgumentNullException("obj"); } Contract.EndContractBlock(); ObjPulse(obj); } private static extern void ObjPulse(Object obj); public static void PulseAll(Object obj) { if (obj == null) { throw new ArgumentNullException("obj"); } Contract.EndContractBlock(); ObjPulseAll(obj); } private static extern void ObjPulseAll(Object obj); public static bool IsEntered(object obj) { if (obj == null) throw new ArgumentNullException("obj"); return IsEnteredNative(obj); } private static extern bool IsEnteredNative(Object obj); }
核心方法就是Enter和Exit,其中lock關鍵字就是這2個方法的一個封裝,剩下的Wait、Pulse和PulseAll也是很重要的方法,可是平時運用的比較少。因此這裏重點說說Wait、Pulse和PulseAll方法。操作系統
線程優先順序: 【等待隊列】->【就緒隊列】->【擁有鎖線程】這個是重點,下文屢次會提到,其中的微妙關係的核心也來源於這個執行順序。
MSDN官方備註:同步的對象包含若干引用,其中包括對當前擁有鎖的線程的引用、對就緒隊列的引用和對等待隊列的引用。個人提醒:競爭對象鎖的線程都是處於就緒隊列中。線程
1.Monitor.Wait方法
當線程調用 Wait 時,它釋放對象的鎖並進入對象的等待隊列,對象的就緒隊列中的下一個線程(若是有)獲取鎖並擁有對對象的獨佔使用。Wait()就是交出鎖的使用權,使線程處於阻塞狀態,直到再次得到鎖的使用權。
2.Monitor.Pulse方法
當前線程調用此方法以便向隊列中的下一個線程發出鎖的信號。接收到脈衝後,等待線程就被移動到就緒隊列中。在調用 Pulse 的線程釋放鎖後,就緒隊列中的下一個線程(不必定是接收到脈衝的線程)將得到該鎖。pulse()並不會使當前線程釋放鎖。code
當一個線程嘗試着lock一個同步對象的時候,該線程就在就緒隊列中排隊。一旦沒人擁有該同步對象,就緒隊列中的線程就能夠佔有該同步對象。這也是咱們平時最常常用的lock方法。爲了其餘的同步目的,佔有同步對象的線程也能夠暫時放棄同步對象,並把本身流放到等待隊列中去,這就是Monitor.Wait;因爲該線程放棄了同步對象,其餘在就緒隊列的排隊者就能夠進而擁有同步對象。比起就緒隊列來講,在等待隊列中排隊的線程更像是二等公民:他們不能自動獲得同步對象,甚至不能自動升艙到就緒隊列。而Monitor.Pulse的做用就是開一次門,使得一個正在等待隊列中的線程升艙到就緒隊列;相應的Monitor.PulseAll則打開門放全部等待隊列中的線程到就緒隊列。對象
class Program { static void Main(string[] args) { new Thread(A).Start(); new Thread(B).Start(); new Thread(C).Start(); Console.ReadLine(); } static object lockObj = new object(); static void A() { lock (lockObj) //進入就緒隊列 { Thread.Sleep(1000); Monitor.Pulse(lockObj); Monitor.Wait(lockObj); //自我流放到等待隊列 } Console.WriteLine("A exit..."); } static void B() { Thread.Sleep(500); lock (lockObj) //進入就緒隊列 { Monitor.Pulse(lockObj); } Console.WriteLine("B exit..."); } static void C() { Thread.Sleep(800); lock (lockObj) //進入就緒隊列 { } Console.WriteLine("C exit..."); } }
假設線程A先獲得了同步對象,它就登記到同步對象lockObj的「擁有者引用」中。線程B和C要求擁有同步對象,他們將在「就緒隊列」排隊,|--(擁有鎖的線程) A | |--(就緒隊列) B,C | |--(等待隊列)。blog
線程A用Pulse發出信號,容許第一個正在"等待隊列"中的線程進入到」就緒隊列「。但因爲等待列是空的,什麼事也沒有發生。線程A用Wait放棄同步對象,並把本身放入"等待隊列"。B,C已經在就緒隊列中,所以其中的一個得以得到同步對象(假定是B)。B成了同步對象的擁有者。C如今仍是候補委員,能夠自動得到空缺。而A則被關在門外,不能自動得到空缺。 |--(擁有鎖的線程) B ||--(就緒隊列) C | |--(等待隊列) A隊列
線程B用Pulse發出信號開門,第一個被關在門外的A被容許放入到就緒隊列,如今C和A都成了候補委員,一旦同步對象空閒,都有機會得它。 |--(擁有鎖的線程) B | |--(就緒隊列) C,A | |--(等待隊列)
進程
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 Program2 { 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); } }
咱們看到了該玩具MyManualEvent實現了類庫中的ManulaResetEvent的功能,但卻更加的輕便,類庫的ManulaResetEvent使用了操做系統內核事件機制,負擔比較大(不算競態時間,ManulaResetEvent是微秒級,而lock是幾十納秒級。例子的WaitOne中先在lock的保護下判斷是否信號綠燈,若是不是則進入等待。所以能夠有多個線程(好比例子中的AB)在等待隊列中排隊。當調用Set的時候,在lock的保護下信號轉綠,並使用PulseAll開門放狗,將全部排在等待隊列中的線程放入就緒隊列,A或B(好比A)因而能夠從新得到同步對象,從Monitor.Wait退出,並隨即退出lock區塊,WaitOne返回。隨後B或A(好比B)重複相同故事,並從WaitOne返回。線程C在myManualEvent.Set()後才執行,它在WaitOne中確信信號燈早已轉綠,因而能夠馬上返回並得以執行隨後的命令。該玩具MyManualEvent能夠用在須要等待初始化的場合,好比多個工做線程都必須等到初始化完成後,接到OK信號後才能開工。該玩具MyManualEvent比起ManulaResetEvent有不少侷限,好比不能跨進程使用,但它演示了經過基本的Monitor命令組合,達到事件機的做用。事件