互斥鎖sync.Mutex是在并發程序中對共享資源進行訪問控制的主要手段,對此Go語言提供了非常簡單易用的機制。sync.Mutex為結構體類型,對外暴露Lock()、Unlock()、TryLock()三種方法,分別用于阻塞加鎖、解鎖、非阻塞加鎖操作(加鎖失敗后快速返回結果不會陷入阻塞狀態)。
sync.Mutex內部實現比較復雜,但是堅持閱讀之后,卻有很大的收益。比如如何設計一個任務調度系統,每個時間點只有一個任務執行,在調度任務時,既需要保證任務執行的效率也需要保證一個任務不會出現餓死的情況,sync.Mutex的內部機制可能會給你一些借鑒經驗。除此之外,還能夠讓你對TryLock有更加深刻的理解和在使用Mutex時的注意點。
版本:go1.24.1
數據結構
//package: pakcage:src\internal\sync\mutex.gotype Mutex struct {state int32sema uint32
}const (mutexLocked = 1 << iota // mutex is lockedmutexWokenmutexStarvingmutexWaiterShift = iotastarvationThresholdNs = 1e6
)
Mutex結構比較簡單,定義了兩個字段:
- state:根據bit位的劃分,表示多種含義。
- sema:信號量,用于管理協程阻塞和喚醒的關鍵機制,確保協程高效調度和喚醒。
state字段通過分割比特位來表示三種狀態和記錄當前等待獲取鎖的協程數量,從低位到高位依次為:
- mutexLocked:1bit位,當前mutex是否被鎖定,0表示未鎖定,1表示鎖定。
- mutexWoken:1bit位,當前是否有協程從阻塞中被喚醒,0表示未被喚醒,1表示有協程被喚醒。
- mutexStarving:1bit位,當前mutex的所處模式,0表示正常模式,1表示饑餓模式。
- ?mutexWaiterShift:位偏移量,利用偏移后的位來記錄等待協程的數量,占用29bit位。
兩種模式
正常模式和饑餓模式是sync.mutex包的精髓,通過這兩種模式來保證性能和公平。
- 正常模式:追求性能,允許新的協程通過自旋和競爭來快速獲取鎖,減少協程的上下文切換開銷。
- 饑餓模式:兜底公平性,確保等待者不被餓死。
在正常模式下,等待者按FIFO順序排隊,但被喚醒的等待者不擁有mutex,并與新到達的goroutines競爭所有權。而新加入的goroutines有一個優勢——它們已經在CPU上運行,并且可能有很多,所以喚醒的等待者很有可能會失敗。在這種情況下,它被排在等待隊列的前面。如果等待者獲得mutex的時間超過1ms,將mutex將切換到饑餓模式。
在饑餓模式下,mutex的所有權直接從正在解鎖的goroutine移交(hand off)給隊列前面的等待者。新到達的goroutines不會嘗試獲取mutex,即使mutex已經解鎖,(新到達的goroutines)也不會嘗試自旋。相反,它們把自己排在等待隊列的尾部。如果一個等待者獲得了mutex的所有權,并且發現以下任一條件:(1)它是隊列中最后一個等待者;(2)它等待的時間少于1毫秒;那么mutex將切換回正常工作模式。
源碼解讀
請訪問github倉庫,以注釋的方式進行解讀,提高閱讀體驗和保證思考的連續性。
倉庫地址:wuqiong818/go-source-interpretation: go語言解讀
參考文章
Go1.24.1源碼
Go專家編程 sync.Mutex章節
【Go萬字洗髓經】Golang中sync.Mutex的單機鎖:實現原理與底層源碼-CSDN博客