简单聊聊C#中lock关键字
為了避免多個線程同時操作同一資源,引起數據錯誤,通常我們會將這個資源加上鎖,這樣在同一時間只能有一個線程操作資源。在C#中我們使用lock關鍵字來鎖定資源,那lock關鍵字是如何實現鎖定的呢?
我們先看一段代碼,非常簡單的單例,相信你閉著眼睛也能寫出來。代碼如下:
上面的代碼我就懶得解釋了。我們重點關注lock(locker)這行,就是這行限制了多個線程對大括號內代碼的同時訪問。下面我們就來講一個原理。不過講原理之前,還得和大家確認一個知識點,其實lock只是語法糖,其實現其實是Monitor類,所以上面的代碼和下面的代碼是相等。代碼如下:
沒有異議的話,我們接著講。我們還是看lock(locker)這一行,簡單地想一想,lock關鍵字把locker對象鎖住了,別的線程就進不來了,那它是怎么鎖的呢?是在對象上打了什么標記嗎?是把線程ID打到標記嗎?我們先看一下堆中locker的對象結構。如下圖:
對象在堆中除了數據區以外,還有兩片區域:MethodTableRef、SyncBlockValue。其中MethodTableRef是指向MethodTable中這個類型(Type)定義,簡單說就是這個對象是什么class定義的,這個不是我們今天討論的重點,就過了。
我們重點看一下SyncBlockValue,這個值為DWORD類型,占用4個字節,也就是32位。這個值主要用來表示對象的不同功能,具體什么功能,要看這個32位如何賦值。一般將32位分為兩段,前6位用以表示不同的功能,后26位用以表示對象的hash值、或者是SyncBlock的索引值。如下圖:
回到lock(locker)這個主題上。其實你也應該猜到,如果想實現lock(locker),只要在locker的前六位功能位上設置一個標識位,標識這個對象已經被鎖住就行。但具體被哪個線程鎖住要記在哪邊呢?這時就要用到后面26位了。這時后26位會指向g_pSyncTable的某一項。g_pSyncTable是CLR維護的一個包SyncBlock項的全局數組。這時的結構如下圖:
我們結合上圖再來回顧一下整個流程:比如有兩個線程:線程A、線程B。線程A執行到lock(locker)這一行時,會先檢查locker的6個功能位中有沒有沒鎖住標識位(假如第五位表示鎖),如果沒有鎖,則將第五位標為1(不一定是1,這里只是舉例),然后到g_pSyncTable數組中申請一個SyncBlock,將當前線程ID等信息記錄在里面,然后將這個SyncBlock的地址賦予locker對象的后26位值,這樣資源就被線程A鎖住了。這時線程B也執行到lock(locker)這一行,它檢查前第五位,發現被鎖住了,就會到SyncBlock檢查鎖住的線程ID是否和自己一致,不一致的話,它就會一直等待,直到線程A釋放鎖。
以上只是粗略地講述一下鎖的底層原理,可能有很多描述不準確的地方,比如哪個功能位表示鎖,其實我也不太清楚,但大概原理應該沒錯。
最后留個問題:如果鎖住的對象同時又想獲取HashCode,該如何存儲并得到呢?
總結
以上是生活随笔為你收集整理的简单聊聊C#中lock关键字的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NET 5.0正式发布,有什么功能特性
- 下一篇: BCVP开发者说第一期:Destiny.