目錄:
1、mutex數(shù)據(jù)結(jié)構(gòu)
2、加解鎖過程
- 2.1、簡單加鎖
- 2.2、加鎖被阻塞
- 2.3、簡單解鎖
- 2.4、解鎖并喚醒協(xié)程
3、自旋過程
- 3.1、什么是自旋
- 3.2、自旋條件
- 3.3、自旋的優(yōu)缺點
4、Mutex模式Normal和Starvation
1、mutex數(shù)據(jù)結(jié)構(gòu)
源碼包 src/sync/mutex.go:Mutex 定義了互斥鎖的數(shù)據(jù)結(jié)構(gòu):
type Mutex struct {
state int32
sema uint32
}
Mutex.state表示互斥鎖的狀態(tài),比如是否被鎖定等;
-
Mutex.sema表示信號量,協(xié)程阻塞等待該信號量,解鎖的協(xié)程釋放信號量從而喚醒等待信號量的協(xié)程;
我們看到Mutex.state是32位的整型變量,內(nèi)部實現(xiàn)時把該變量分成四份,用于記錄Mutex的四種狀態(tài)。 下圖展示Mutex的內(nèi)存布局:
mutex-1.jpg Locked: 表示該Mutex是否已被鎖定,0:沒有鎖定 1:已被鎖定;
Woken: 表示是否有協(xié)程已被喚醒,0:沒有協(xié)程喚醒 1:已有協(xié)程喚醒,正在加鎖過程中;
Starving:表示該Mutex是否處理饑餓狀態(tài), 0:沒有饑餓 1:饑餓狀態(tài),說明有協(xié)程阻塞了超過1ms;
Waiter: 表示阻塞等待鎖的協(xié)程個數(shù),協(xié)程解鎖時根據(jù)此值來判斷是否需要釋放信號量;
協(xié)程之間搶鎖實際上是搶給Locked賦值的權(quán)利,能給Locked域置1,就說明搶鎖成功。搶不到的話就阻塞等待 Mutex.sema信號量,一旦持有鎖的協(xié)程解鎖,等待的協(xié)程會依次被喚醒。Woken和Starving主要用于控制協(xié)程間的搶鎖過程。
2、加解鎖過程:
Mutext對外提供兩個方法,實際上也只有這兩個方法:
- Lock() : 加鎖方法;
- Unlock(): 解鎖方法 ;
面我們分析一下加鎖和解鎖的過程,加鎖分成功和失敗兩種情況,成功的話直接獲取鎖,失敗后當前協(xié)程被阻塞,同樣,解鎖時跟據(jù)是否有阻塞協(xié)程也有兩種處理。
2.1、簡單加鎖
假定當前只有一個協(xié)程在加鎖,沒有其他協(xié)程干擾,那么過程如下圖所示:

加鎖過程會去判斷Locked標志位是否為0,如果是0則把Locked位置1,代表加鎖成功。從上圖可見,加鎖成功后, 只是Locked位置1,其他狀態(tài)位沒發(fā)生變化。
2.2、加鎖被阻塞:
假定加鎖時,鎖已被其他協(xié)程占用了,此時加鎖過程如下圖所示:

從上圖可看到,當協(xié)程B對一個已被占用的鎖再次加鎖時,Waiter計數(shù)器增加了1,此時協(xié)程B將被阻塞,直到 Locked值變?yōu)?后才會被喚醒。
2.3、簡單解鎖:
假定解鎖時,沒有其他協(xié)程阻塞,此時解鎖過程如下圖所示:

由于沒有其他協(xié)程阻塞等待加鎖,所以此時解鎖時只需要把Locked位置為0即可,不需要釋放信號量。
2.4、解鎖并喚醒協(xié)程:
假定解鎖時,有1個或多個協(xié)程阻塞,此時解鎖過程如下圖所示:

協(xié)程A解鎖過程分為兩個步驟,一是把Locked位置0,二是查看到Waiter>0,所以釋放一個信號量,喚醒一個阻塞的 協(xié)程,被喚醒的協(xié)程B把Locked位置1,于是協(xié)程B獲得鎖。
3、自旋過程:
加鎖時,如果當前Locked位為1,說明該鎖當前由其他協(xié)程持有,嘗試加鎖的協(xié)程并不是馬上轉(zhuǎn)入阻塞,而是會持續(xù)
的探測Locked位是否變?yōu)?,這個過程即為自旋過程。 自旋時間很短,但如果在自旋過程中發(fā)現(xiàn)鎖已被釋放,那么協(xié)程可以立即獲取鎖。此時即便有協(xié)程被喚醒也無法獲取鎖,只能再次阻塞。
自旋的好處是,當加鎖失敗時不必立即轉(zhuǎn)入阻塞,有一定機會獲取到鎖,這樣可以避免協(xié)程的切換。
3.1、什么是自旋:
自旋對應(yīng)于CPU的”PAUSE”指令,CPU對該指令什么都不做,相當于CPU空轉(zhuǎn),對程序而言相當于sleep了一小段時間,時間非常短,當前實現(xiàn)是30個時鐘周期。 自旋過程中會持續(xù)探測Locked是否變?yōu)?,連續(xù)兩次探測間隔就是執(zhí)行這些PAUSE指令,它不同于sleep,不需要將協(xié)程轉(zhuǎn)為睡眠狀態(tài)。
3.2、自旋條件:
加鎖時程序會自動判斷是否可以自旋,無限制的自旋將會給CPU帶來巨大壓力,所以判斷是否可以自旋就很重要了。 自旋必須滿足以下所有條件:
- 自旋次數(shù)要足夠小,通常為4,即自旋最多4次 ;
- CPU核數(shù)要大于1,否則自旋沒有意義,因為此時不可能有其他協(xié)程釋放鎖;
- 協(xié)程調(diào)度機制中的Process數(shù)量要大于1,比如使用GOMAXPROCS()將處理器設(shè)置為1就不能啟用自旋;
- 協(xié)程調(diào)度機制中的可運行隊列必須為空,否則會延遲協(xié)程調(diào)度;
可見,自旋的條件是很苛刻的,總而言之就是不忙的時候才會啟用自旋。
3.3、自旋的優(yōu)缺點:
- 優(yōu)點:自旋的優(yōu)勢是更充分的利用CPU,盡量避免協(xié)程切換。因為當前申請加鎖的協(xié)程擁有CPU,如果經(jīng)過短時間的自旋可以 獲得鎖,當前協(xié)程可以繼續(xù)運行,不必進入阻塞狀態(tài)。
- 缺點:如果自旋過程中獲得鎖,那么之前被阻塞的協(xié)程將無法獲得鎖,如果加鎖的協(xié)程特別多,每次都通過自旋獲得鎖,那么之前被阻塞的進程將很難獲得鎖,從而進入饑餓狀態(tài)。
為了避免協(xié)程長時間無法獲取鎖,自1.8版本以來增加了一個狀態(tài),即Mutex的Starving狀態(tài)。這個狀態(tài)下不會自 旋,一旦有協(xié)程釋放鎖,那么一定會喚醒一個協(xié)程并成功加鎖。
4、Mutex模式Normal和Starvation:
1)、normal模式:
默認情況下,Mutex的模式為normal。 該模式下,協(xié)程如果加鎖不成功不會立即轉(zhuǎn)入阻塞排隊,而是判斷是否滿足自旋的條件,如果滿足則會啟動自旋過。
2)、Starvation模式:
自旋過程中能搶到鎖,一定意味著同一時刻有協(xié)程釋放了鎖,我們知道釋放鎖時如果發(fā)現(xiàn)有阻塞等待的協(xié)程,還會釋放一個信號量來喚醒一個等待協(xié)程,被喚醒的協(xié)程得到CPU后開始運行,此時發(fā)現(xiàn)鎖已被搶占了,自己只好再次阻塞, 不過阻塞前會判斷自上次阻塞到本次阻塞經(jīng)過了多長時間,如果超過1ms的話,會將Mutex對象的state字段標記為”饑餓”模式,然后再阻塞。
處于饑餓模式下,不會啟動自旋過程,也即一旦有協(xié)程釋放了鎖,那么一定會喚醒協(xié)程,被喚醒的協(xié)程將會成功獲取 鎖,同時也會把等待計數(shù)減1。
3)、Woken狀態(tài):
Woken狀態(tài)用于加鎖和解鎖過程的通信,舉個例子,同一時刻,兩個協(xié)程一個在加鎖,一個在解鎖,在加鎖的協(xié)程可 能在自旋過程中,此時把Woken標記為1,用于通知解鎖協(xié)程不必釋放信號量了,好比在說:你只管解鎖好了,不必釋放信號量,我馬上就拿到鎖了。
