這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
sync包給我們方便的提供了一組同步語意相關的標準庫,下面我們重點看下sync.Mutex互斥是如何?的。 我本機源碼安裝路徑在/usr/local/go,這sync.Mutex(golang 1.3版本)涉及到的相關代碼主要有:
/usr/local/go/src/pkg/sync/mutex.go
/usr/local/go/src/pkg/sync/runtime.go
/usr/local/go/src/pkg/runtime/sema.goc
首先是mutex.go:
// A Mutex is a mutual exclusion lock. // Mutexes can be created as part of other structures; // the zero value for a Mutex is an unlocked mutex. type Mutex struct { state int32 sema uint32 }
首先我們看到Mutex是由state和sema兩個整形組成,我們不難推測出,mutex內部實現依賴的是訊號量用於goroutine的喚醒操作,state就是對鎖搶佔者的統計,其實這種方式是採用E.W.Dijkstra在1965年提出的一種方法,用整形變數累計喚醒計數。見論文:semaphore(之後我單寫文章分析)。
const ( mutexLocked = 1 << iota // mutex is locked mutexWoken mutexWaiterShift = iota )
mutexLocked = 1(二進位是1):表示mutex處於鎖狀態。
mutexWoken = 2(二進位是10):表示mutex處於喚醒狀態。
mutexWaiterShift = 2(二進位10):表示等待持有鎖需要累計計數的左移位。
接下來是核心的Lock:
// Lock locks m. // If the lock is already in use, the calling goroutine // blocks until the mutex is available. func (m *Mutex) Lock() { // Fast path: grab unlocked mutex. if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) { if raceenabled { raceAcquire(unsafe.Pointer(m)) } return }
raceenabled相關的代碼全部忽略,這是golang內部使用thread-sanitizer用於掃描安全執行緒問題的診斷代碼。
首先代碼使用cpu的CAS指令,修改state值,如果值為0的時候,那麼置state為mutexLocked狀態(即為:1),如果成功,表示鎖爭用成功,直接return。
awoke := false
awoke表示由於訊號量release導致的goroutine喚醒,在for迴圈的底部我們看到runtime_Semacquire返回以後會被設定成true。
for { old := m.state new := old | mutexLocked if old&mutexLocked != 0 { new = old + 1<<mutexWaiterShift }
進入for迴圈開始一直爭用,知道成功。對於爭用失敗的人,即state!=0,old儲存當前state值,new儲存old和mutexLocked或運算,因為state可能被立馬釋放掉,因此需要先把locked bit位給設定上,也在後續retry lock的時候new會被直接CAS到state中去。
old&mutexLocked!=0表示,如果還存在鎖未釋放,new值需要新增搶佔者計數(1<<2其實就是+4操作)。
if awoke { // The goroutine has been woken from sleep, // so we need to reset the flag in either case. new &^= mutexWoken }
如果是被喚醒的操作,我們需要把mutexWoken的bit位給抹去,這裡使用一個異或操作和與操作來完成(先對new和mutexWoken進行異或操作再和new進行與操作)。
if atomic.CompareAndSwapInt32(&m.state, old, new) { if old&mutexLocked == 0 { break } runtime_Semacquire(&m.sema) awoke = true } }
之後到了retry lock的步驟,前面說了,因為可能鎖持有人立馬就釋放了鎖,因此做一步retry操作可以儘可能少的使用訊號量來sleep和wakeup的開銷;另外喚醒操作也需要重新進行新一輪的CAS判斷。
如果當前state和old相等表示沒有其他爭用者修改state值(有的話重新來過),而old&mutexLocked = 0 意味著鎖其實已經被釋放,那麼上一步的CAS又把鎖設定成locked狀態,函數break,持鎖成功,否則進行訊號量的DOWN操作。
semaphore的DOWN操作,檢測sema值是否大於0,如果大於0,原子減一,goroutine進入ready狀態,繼續爭用鎖;否則goroutine進入sleep等待喚醒狀態。
再看看對應的Unlock操作:
// Unlock unlocks m. // It is a run-time error if m is not locked on entry to Unlock. // // A locked Mutex is not associated with a particular goroutine. // It is allowed for one goroutine to lock a Mutex and then // arrange for another goroutine to unlock it. func (m *Mutex) Unlock() { if raceenabled { _ = m.state raceRelease(unsafe.Pointer(m)) } // Fast path: drop lock bit. new := atomic.AddInt32(&m.state, -mutexLocked) if (new+mutexLocked)&mutexLocked == 0 { panic("sync: unlock of unlocked mutex") }
第一步是原子操作把locked bit位給抹除賦值給new,為了避免多次解鎖操作使用new重新置位locked和mutexLocked進行與判斷來校正,但是似乎這裡有ABA的BUG,因為有可能連續N次的Unlock,雖然有一部分panic,但是另外一個人會成功。
old := new for { // If there are no waiters or a goroutine has already // been woken or grabbed the lock, no need to wake anyone. if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken) != 0 { return } // Grab the right to wake someone. new = (old - 1<<mutexWaiterShift) | mutexWoken if atomic.CompareAndSwapInt32(&m.state, old, new) { runtime_Semrelease(&m.sema) return } old = m.state }
old儲存new的值,進入for迴圈,為了避免不必要的喚醒操作,這樣判斷了是否有等著喚醒的人以及是否存在爭用和已經喚醒的情況。
old>>mutexWaiterShift==0:表示old當前已經沒有任何waiters。
old&(mutexLocked|mutexWorken)!=0:表示存在爭用和已經觸發了喚醒,考慮下執行完new := atomic.AddInt32(&m.state, -mutexLocked)之後,突然有人嘗試加鎖,那麼Lock中的new = old + 4被賦值給state,此時已經爭用者立馬進入retry lock的邏輯,最終會加鎖成功(注意此時只有一個ready狀態的goroutine)
new = (old - 1<<mutexWaiterShift) | mutexWoken
表示new值先減去一個waiter的計數,然後把woken置位。
而還有一種情況是執行完atomic.CompareAndSwapInt32(&m.state, old, new)操作失敗,state處於爭用狀態,也就無需重複喚醒了,因為爭用者可能已經搶到鎖了(可見作者這塊考慮的相當的完善)。
而如果CAS成功,最終會調用runtime_Semrelease來進行訊號量操作喚醒goroutine,而原子操作失敗的話只能重頭來過了。
整個的mutex加鎖和解鎖分析完畢,後續我會重點講一下runtime_Semacquire和runtime_Semrelease在golang runtime中是如何調度goroutine的。