C# Monitor實現

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命令組合,達到事件機的做用。事件

相關文章
相關標籤/搜索