您好,登錄后才能下訂單哦!
本篇內容介紹了“Golang sync包之sync.Mutex怎么使用”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
type Mutex struct { state int32 sema uint32 }
我們可以發現sync.Mutex的數據結構十分簡單, 他只有兩個字段
state: 一個32位整數, 表示了當前鎖的狀態
sema: 他代表一個信號量(信號量是一個無符號整數,OS對信號量提供了能使其原子性自增/自減的PV操作, 可以通過信號量來實現互斥)
state的含義如圖
他的低三位分別是Locked, Woken, Starving. 剩余28位表示在當前Mutex中阻塞的goroutine數目
Locked: 當前Mutex是否處于上鎖狀態, 0: 上鎖, 1: 上鎖.
Woken: 當前Mutex是否存在goroutine被喚醒, 0: 沒有, 1: 存在被喚醒的goroutnine正在上鎖.
Starving: 當Mutex處于何種模式, 0: 正常模式, 1: 饑餓模式
當我們聲明一個變量但不給他賦值時, 他會被默認附上初始值, 這個初始值對于指針類型是nil, 而其他類型則是其零值.
因此, 當我們僅聲明sync.Mutex時, 他會被默認賦值{state:0,sema:0}
, 而這個值恰好表示初始的鎖狀態. 所以 ,我們可以僅在聲明后直接使用sync.Mutex.
在了解正常模式和饑餓模式前, 我們先來看下搶占式和非搶占式.
搶占式和非搶占式是調度的兩種方式
搶占式: 當一個新goroutine請求鎖時, 他會和當前被喚醒的goroutine進行競爭, 競爭成功就獲取鎖. 非搶占式: 如果阻塞隊列中存在有其他goroutine, 那么新請求鎖的goroutine會直接進入阻塞隊列排隊.
一般情況下, sync.Mutex處于正常模式, 在該模式下, 鎖的獲取方式為搶占式調度. 而一般情況下, 因為新請求鎖的goroutine正在持有CPU且可能不止一個, 這就導致阻塞隊列中新被喚醒的goroutine是難以搶占過新請求鎖的goroutine的, 從而導致饑餓現象.
為了解決這種饑餓現象, go語言在go1.9的時候引入了饑餓模式, 在饑餓模式下, 鎖的獲取方式為非搶占式調度.
正常模式->饑餓模式:
當一個goroutine在阻塞隊列等待超過1ms后, 他就會修改state使鎖的狀態變為饑餓模式
饑餓模式->正常模式:
當阻塞隊列中某個goroutine阻塞時間小于1ms時
阻塞隊列中重新變為空時
Lock方法有兩種上鎖方式, 快速上鎖和慢速上鎖
快速上鎖
假如當前sync.Mutex的state=0, 那么就意味著當前sync.Mutex尚未被任何goroutine持有且阻塞隊列中沒有任何goroutine.
那么當前請求鎖的goroutine就會通過CAS的方式修改state=1(意味著上鎖), 然后返回.
慢速上鎖
否則, 會調用lockSlow()方法來慢速上鎖.
首先嘗試通過自旋的方式獲取鎖, 在如下四個條件全部滿足時會繼續自旋, 如果成功通過自旋獲取鎖, 就直接返回
state不處于饑餓態
自旋次數小于4次
當前進程運行于多CPU主機
存在至少一個正在運行且工作隊列為空的控制器P
之后會根據當前鎖的不同狀態做出不同的行為, 這里我們分類討論.
正常狀態
我們只截取少量的核心代碼.
//如果處于正常態, 那么我們修改新狀態為上鎖 if old&mutexStarving == 0 { new |= mutexLocked } //通過cas的方式修改當前鎖狀態 if atomic.CompareAndSwapInt32(&m.state, old, new) { //如果舊狀態處于正常態且未上鎖, 那么就意味著當前線程搶占到了鎖, 直接返回 if old&(mutexLocked|mutexStarving) == 0 { break //這里的break可以理解為return, 不用return是因為最后有個對race.Enabled的判斷, 用于競態檢測 } ...... }
如果處于正常狀態, 那么允許鎖搶占, 我們先把新的鎖狀態修改為直接上鎖, 這是因為假如他之前處于上鎖態, 那么之后也會處于上鎖態, 而如果處于未上鎖狀態, 那么當前goroutine會為其上鎖, 因此鎖的新狀態一定處于上鎖態.
然后我們通過CAS的方式用新狀態替換舊狀態. 替換成功后判斷old(更新前的狀態)是否處于正常態且未上鎖, 如果是, 那么說明是當前goroutine上的鎖, 這也就意味著當前goroutine成功獲取鎖了, 那么直接返回.
否則會調用runtime_SemacquireMutex
來在sema信號量下阻塞當前goroutine.
饑餓狀態
饑餓狀態會直接調用runtime_SemacquireMutex
來在sema信號量下阻塞當前goroutine.
被喚醒后
在某個goroutine被喚醒后, 他會判斷自己阻塞時間是否超過1ms, 如果超過, 則切換為饑餓模式, 否則判斷自己是否處于饑餓模式且阻塞的時間小于1ms, 如果處于饑餓模式且阻塞時間小于1ms, 那么就退出饑餓模式.
unlock()方法也分為快速解鎖和慢速解鎖兩部分
快速解鎖
我們直接讓new=當前狀態-mutexLocked
如果new=0, 則意味著只有當前goroutine在持有鎖, 且無任何goroutine在等待鎖, 那么直接CAS修改m.state=new然后返回即可.
慢速解鎖
否則調用unlockSlow()函數來解鎖, unlockSlow()函數也會根據當前Mutex的不同狀態做出不同的行為
不過首先, unlockSlow()會判斷當前Mutex是否處于上鎖態, 如果我們對未上鎖的Mutex調用Unlock()函數, 會爆出sync: unlock of unlocked mutex
的panic.
正常狀態
通過state的前28位判斷當前等待鎖的goroutine是否為0, 如果是, 那么直接解鎖返回.
否則通過CAS的方式修改當前Mutex狀態為new, 如果修改成功, 那么將釋放一個信號量來隨機喚醒一個阻塞在sema中的goroutine
//false意味著隨機喚醒 runtime_Semrelease(&m.sema, false, 1)
饑餓狀態
如果當前Mutex處于饑餓狀態, 那么說明一定存在阻塞的goroutine, 將釋放一個信號量來喚醒sema中第一個阻塞的goroutine
//true為順序喚醒 runtime_Semrelease(&m.sema, true, 1)
TryLock是嘗試上鎖, 如果上鎖成功, 返回true, 否則返回false, 他的實現十分簡單.
判斷當前狀態Mutex的狀態是否是饑餓態或者已上鎖, 如果是, 則直接返回false
通過CAS的方式嘗試為Mutex上鎖, 上鎖成功則返回true,否則返回false
“Golang sync包之sync.Mutex怎么使用”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。