【Windows】線程漫談——.NET線程同步之Monitor和lock【Windows】線程漫談——線程同步之Slim讀/寫鎖

來源:互聯網
上載者:User

摘要: 本系列意在記錄Windwos線程的相關知識點,包括線程基礎、線程調度、線程同步、TLS、線程池等。

從這篇開始,線上程同步的方法上,開始在.NET平台上做個總結,同時對比Windows原生的API方法。你可以發現其中的聯絡。

 

.NET中的Monitor和lock

相信很多看官早已對此十分熟悉了。本文作為總結性的文章,有一些篇幅將對比Monitor和關鍵段的關係。由於lock就是Monitor,所以先從Monitor說起,通常Monitor是像下面這樣使用的:

Monitor.Entry(lockObj);try{    // lockObj的同步區}catch(Exception e){    // 異常處理代碼}finally{    Monitor.Exit(lockObj);  // 解除鎖定}

當某個線程在Monitor.Entry返回後就獲得了對其中lockObj的存取權限,其他試圖擷取lockObj的線程將被阻塞,直到線程調用Monitor.Exit釋放lockObj的所有權。這意味著下面三點:

  • 如果lockObj是閒置,那麼第一個調用Entry的線程將立即獲得lockObj;
  • 如果調用Entry的線程已經獲准訪問lockObj,那麼不會阻塞;
  • 如果調用Entry時lockObj已被其他線程鎖定,則線程等待直到lockObj解鎖;

事實上其中的第二點是個重要的特徵,這種情況將發生在遞迴的情況下。Monitor應該會記錄線程獲准訪問lockObj的次數,以正確的對鎖定次數進行遞減。

我花了一些時間研究Monitor到底對應底層是什麼實現方式,但是我並沒有找到證據證明Monitor和關鍵段有什麼必然聯絡。但是從表象上看,Monitor的API方式和關鍵段如此相似,而且上述的三個特點也幾乎完全一致,況且MSDN也把Monitor表述成Critical Section,因此,暫且認為Monitor就是關鍵段的封裝吧!

在我之前的文章【Windows】線程漫談——線程同步之關鍵段中詳細介紹了Windows API關鍵段,下面列出這兩種API的對比:

.NET Monitor API Windows API
Monitor.Entry(lockObj) EnterCriticalSection(&cs)
Monitor.Exit(lockObj) LeaveCriticalSection(&cs)
Monitor.TryEntry(lockObj) TryEnterCriticalSection(&cs)
-- InitializeCriticalSection(&cs);
-- DeleteCriticalSection(&cs);
-- InitializeCriticalSectionAndSpinCount
-- SetCriticalSectionSpinCount
Monitor.Pulse --
Monitor.Wait --

可以看到Monitor簡化了關鍵段的使用,而且還提供了額外的Wait和Pulse方法(因為不常用,因此這裡不展開了)。但是如果Monitor真的就是關鍵段實現的話,Monitor卻不能讓我們設定旋轉鎖的嘗試次數,這是一個缺陷。

關於Wait和Pulse順便提一下,我個人認為是條件變數的一個替代方案。關於條件變數詳見【Windows】線程漫談——線程同步之Slim讀/寫鎖。

最後再次強調,這裡的對比只是本人一廂情願,未必說Monitor真的就是關鍵段!

針對Monitor鎖定的lockObj有如下問題需要注意:

  • lockObj不能是實值型別,因為這裡會被裝箱,而每次裝箱的引用不同,因此C#在編譯階段就保證了這種限制
  • lockObj最好不要是public對象,因為可能會導致死結,比如下面這個極端的情況:
public class Foo{    public void Bar()    {        lock (this)        {            Console.WriteLine("Class:Foo:Method:Bar");        }    }}public class MyClient{    public void Test()    {        Foo f = new Foo();        lock (f) //獲准了f對象        {            ThreadStart ts = new ThreadStart(f.Bar);            Thread t = new Thread(ts);            t.Start(); //新線程執行Bar方法需要獲得f的存取權限,但是已被當前線程鎖定,新線程將阻塞            t.Join(); //新線程將無法返回,死結        }    }}
  • lockObj最好不要是字串,由於字串駐留的原因,可能導致死結:
public class Foo    {        public void Bar()        {            lock ("Const")//Const將駐留            {                Console.WriteLine("Class:Foo:Method:Bar");            }        }    }    public class MyClient    {        private string lockObj = "Const";        public void Test()        {            Foo f = new Foo();            lock (lockObj) //由於lockObj是"Const","Const"被駐留,所以實際上lock是同一個對象            {                ThreadStart ts = new ThreadStart(f.Bar);                Thread t = new Thread(ts);                t.Start(); //新線程執行Bar方法需要獲得lockObj的存取權限,但是已被當前線程鎖定,新線程將阻塞                t.Join(); //新線程將無法返回,死結            }        }    }

 

上面兩個例子已經用了lock而不是Monitor,事實上,lock經過編譯後就是Monitor,但是lock無法使用Monitor.TryEntry:

.try{ ... IL_0037: call void [mscorlib]System.Threading.Monitor::Enter(object, bool&) ...} // end .tryfinally{ ... IL_0069: call void [mscorlib]System.Threading.Monitor::Exit(object) ...}

 

最後,設計一個簡單的帶一個緩衝隊列的Log方法,要求安全執行緒,下面給出C#的實現(在前面的【Windows】線程漫談——線程同步之關鍵段利用關鍵段給出了C++的實現,這裡的代碼結構幾乎一樣,注釋就省略了):

public class LogInfo    {        public int Level{get;set;}        public string Message{get;set;}    }    public class Log    {        private static List<LogInfo> LogQueue = new List<LogInfo>();        private static object _lockLog = new object();        private static object _lockQueue = new object();        public void Log(int Level, string Message)        {            if (Monitor.TryEnter(_lockLog))            {                Monitor.Enter(_lockQueue);                foreach (var l in LogQueue)                {                    LogInternal(l.Level, l.Message);                 }                LogQueue.Clear();                Monitor.Exit(_lockQueue);                LogInternal(Level, Message);                Monitor.Exit(_lockLog);            }            else            {                Monitor.Enter(_lockQueue);                LogQueue.Add(new LogInfo {                     Level = Level,                    Message = Message                });                Monitor.Exit(_lockQueue);             }        }        protected virtual void LogInternal(int Level, string Message)        {            //真實的log動作可能會耗費非常長的時間         }    }

勞動果實,轉載請註明出處:http://www.cnblogs.com/P_Chou/archive/2012/07/18/monitor-in-net-thread-sync.html

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.