
前言
你知道object lock底層怎麼實作,可重入鎖是底層是怎麼運作的嗎?
本篇就跟大家分享這些細節.
可重入鎖Demo
1 | class Program |
上面這段程式碼,同時間會由2個Thread來呼叫處理TryLockDemo
方法.
主要是演示lock中在對於同一個object lock一次且在multiple-Thread中會怎麼運作
為什麼Thread 1釋放first lock時,Thread 2會繼續blocking並等待Thread 1釋放second lock?
object中Syncblk
在回答上面問題前,我們必須先了解Syncblk這個區塊
每個Object Instance都有的底層資訊
- Syncblk:掌管指向Syncblk Entry Index和HashCode資料
- TypeHandle:存放對應Method Table資訊
TypeHandle不是本次介紹範疇就不多說了
每個Object都有Object Header (syncblk + TypeHandle) 8 bytes
在MSDN有一張圖詳細描述Syncblk
下圖是我畫重點流程和關係
如果對於物件使用lock Syncblk會存放本次使用TheadID,存放指向Syncblk Entry Table.
Syncblk Entry Table是一個全域的物件,掌管物件跟syncblk對應資訊(串聯lock中繼資料表),用指針指向物件所屬的syncBlock.
syncBlock中會存放幾個重要成員變數
- ThreadID:當前佔有的ThreadID
- m_Recursion:當前佔有的ThreadID獲取幾次Lock
- m_appDomainIndex:當前AppDomain標示
- m_lockState:目前lock佔有狀態(int 0代表可用,1代表不可用)
syncBlock內置有一個FIFO等待鏈結表的排隊隊列,將每個等待獲取lock的Thread封裝成一個Node
下面部分會跟大家介紹cpp核心解鎖
LockState object
m_lockState這個變數帳管syncblk鎖狀態,對於Lock來說至關重要
下面是原始碼,裡面涉及許多邏輯運算我不打算一一解說 有興趣的可以自行查看
主要可以看到LockState.m_state
初始值設定成0
1 | class LockState |
source code
Entry Lock cpp code
下面是CLR獲取Lock時核心程式碼
- 當前sync block物件沒有任何Thread佔有且是未上鎖狀態才會進入上鎖環節.
InterlockedTryLock_Or_RegisterWaiter
呼叫此方法內部會做CAS所以狀態具有Atomic.
使用CAS & Volatile來達到變數Atomic & 可見性
- 當前sync block物件是上鎖狀態但佔有Thread不是自己就會呼叫
EnterEpilog
方法會執行把此Thread加入ThreadQueue
等待(FIFO),lock Thread完成發出signal讓後續Threads可以繼續動作. - 當前sync block物件事由當前Thread擁有還在上鎖中,就把m_Recursion++(註記目前重入幾次,需要在釋放把m_Recursion設定成0才會釋放sync block)
1 | void AwareLock::Enter() |
source code
Release Lock cpp code
在呼叫syncblk物件AwareLock::Leave
方法,主要是透過LeaveHelper
來判定解鎖是否成功.
1 | BOOL AwareLock::Leave() |
syncblk.cpp (AwareLock::Leave)
一開始要先判斷目前解鎖的Thread是否和syncblk佔有的Thread相同,如果不同就回傳AwareLock::LeaveHelperAction_Error
後續會判斷是否所有重入鎖都是放完畢(if (--m_Recursion == 0)
),如果都是放完畢就會把m_HoldingThread
釋放,讓其他Thread可以擁有並接續判斷是否有其他Thread在等待此資源,有的話回傳AwareLock::LeaveHelperAction_Signal
代表要通知其他Thread爭取此syncblk物件
1 | FORCEINLINE AwareLock::LeaveHelperAction AwareLock::LeaveHelper(Thread* pCurThread) |
syncblk source code(AwareLock::LeaveHelper)
LockState::InterlockedUnlock
InterlockedUnlock
方法會將LockState.m_state
減1(具有Atomic),把狀態設定成0讓其他人可以獲得此物件.
1 | FORCEINLINE bool AwareLock::LockState::InterlockedUnlock() |
補充說明 Lock Wait環節
上面有說假如有一個SyncBlock
目前已經有Thread在使用中,其他Thread如果要嘗試存取會進入等待鏈結表進行等待.
SyncBlock
內部維護一個重要成員變數SLink
當作指針,指向WaitEventLink
使用鏈結表.
1 | // We can't afford to use an SList<> here because we only want to burn |
WaitEventLink
程式碼
1 | // Used inside Thread class to chain all events that a thread is waiting for by Object::Wait |
下面是ThreadQueue的DequeueThread
& EnqueueThread
實作
DequeueThread
:透過SLink
取得下一個等待的Wait Thread.EnqueueThread
:把新加入等待Thread透過Link reference point,加入到WaitQueue節點之後
1 | // Unlink the head of the Q. We are always in the SyncBlock's critical |
之前有說到Threadret = m_SemEvent.Wait(timeOut, TRUE);
會等待訊號發出,假如不幸同時間有多個Thread在爭搶又搶輸了,就會進入SpinLock等待會透過CLREventBase::WaitEX,最後呼叫PalRedhawkUnix等待再進入Wait環節.
1 | extern "C" UInt32 WaitForSingleObjectEx(HANDLE handle, UInt32 milliseconds, UInt32_BOOL alertable) |
小結
經過上面說明相信大家對於一開始說的可重入鎖,上鎖原理有了些許了解
下面是我畫出上鎖對於重入鎖syncblk物件狀態圖流程圖
在object Instance的sync block index區塊除了會存放lock使用Thread(sync table index)外,HashCode也是存在上面(此區塊共有32 bit,其中26 bit,有時會給呼叫GetHashCode時存放)因為不是這次主題我就不多說了.
本次使用sample在
https://github.com/isdaniel/BlogSample/tree/master/src/Samples/DeepKnowLock
此文作者:Daniel Shih(石頭)
此文地址: https://isdaniel.github.io/lock-deepknow/
版權聲明:本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 TW 許可協議。轉載請註明出處!