## 1 Mutex數據結構
### 1.1 Mutex結構體
源碼包`src/sync/mutex.go:Mutex`定義了互斥鎖的數據結構:
~~~go
type Mutex struct {
state int32
sema uint32
}
~~~
* Mutex.state表示互斥鎖的狀態,比如是否被鎖定等。
* Mutex.sema表示信號量,協程阻塞等待該信號量,解鎖的協程釋放信號量從而喚醒等待信號量的協程。
我們看到Mutex.state是32位的整型變量,內部實現時把該變量分成四份,用于記錄Mutex的四種狀態。
下圖展示Mutex的內存布局:

* Locked: 表示該Mutex是否已被鎖定,0:沒有鎖定 1:已被鎖定。
* Woken: 表示是否有協程已被喚醒,0:沒有協程喚醒 1:已有協程喚醒,正在加鎖過程中。
* Starving:表示該Mutex是否處于饑餓狀態,0:沒有饑餓 1:饑餓狀態,說明有協程阻塞了超過1ms。
* Waiter: 表示阻塞等待鎖的協程個數,協程解鎖時根據此值來判斷是否需要釋放信號量。
協程之間搶鎖實際上是搶給Locked賦值的權利,能給Locked域置1,就說明搶鎖成功。搶不到的話就阻塞等待Mutex.sema信號量,一旦持有鎖的協程解鎖,等待的協程會依次被喚醒。
Woken和Starving主要用于控制協程間的搶鎖過程,后面再進行了解。
### 1.2 Mutex方法
Mutex對外提供兩個方法,實際上也只有這兩個方法:
* Lock() : 加鎖方法
* Unlock(): 解鎖方法
下面我們分析一下加鎖和解鎖的過程,加鎖分成功和失敗兩種情況,成功的話直接獲取鎖,失敗后當前協程被阻塞,同樣,解鎖時根據是否有阻塞協程也有兩種處理。
## 3 加解鎖過程
## 3.1 簡單加鎖
假定當前只有一個協程在加鎖,沒有其他協程干擾,那么過程如下圖所示:

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

從上圖可看到,當協程B對一個已被占用的鎖再次加鎖時,Waiter計數器增加了1,此時協程B將被阻塞,直到Locked值變為0后才會被喚醒。
## 3.3 簡單解鎖
假定解鎖時,沒有其他協程阻塞,此時解鎖過程如下圖所示:

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

協程A解鎖過程分為兩個步驟,一是把Locked位置0,二是查看到Waiter>0,所以釋放一個信號量,喚醒一個阻塞的協程,被喚醒的協程B把Locked位置1,于是協程B獲得鎖。
## 4 自旋過程
加鎖時,如果當前Locked位為1,說明該鎖當前由其他協程持有,嘗試加鎖的協程并不是馬上轉入阻塞,而是會持續的探測Locked位是否變為0,這個過程即為自旋過程。
自旋時間很短,但如果在自旋過程中發現鎖已被釋放,那么協程可以立即獲取鎖。此時即便有協程被喚醒也無法獲取鎖,只能再次阻塞。
自旋的好處是,當加鎖失敗時不必立即轉入阻塞,有一定機會獲取到鎖,這樣可以避免協程的切換。
## 4.1 什么是自旋
自旋對應于CPU的”PAUSE”指令,CPU對該指令什么都不做,相當于CPU空轉,對程序而言相當于sleep了一小段時間,時間非常短,當前實現是30個時鐘周期。
自旋過程中會持續探測Locked是否變為0,連續兩次探測間隔就是執行這些PAUSE指令,它不同于sleep,不需要將協程轉為睡眠狀態。
## 4.2 自旋條件
加鎖時程序會自動判斷是否可以自旋,無限制的自旋將會給CPU帶來巨大壓力,所以判斷是否可以自旋就很重要了。
自旋必須滿足以下所有條件:
* 自旋次數要足夠小,通常為4,即自旋最多4次
* CPU核數要大于1,否則自旋沒有意義,因為此時不可能有其他協程釋放鎖
* 協程調度機制中的Process數量要大于1,比如使用GOMAXPROCS()將處理器設置為1就不能啟用自旋
* 協程調度機制中的可運行隊列必須為空,否則會延遲協程調度
可見,自旋的條件是很苛刻的,總而言之就是不忙的時候才會啟用自旋。
## 4.3 自旋的優勢
自旋的優勢是更充分的利用CPU,盡量避免協程切換。因為當前申請加鎖的協程擁有CPU,如果經過短時間的自旋可以獲得鎖,當前協程可以繼續運行,不必進入阻塞狀態。
## 4.4 自旋的問題
如果自旋過程中獲得鎖,那么之前被阻塞的協程將無法獲得鎖,如果加鎖的協程特別多,每次都通過自旋獲得鎖,那么之前被阻塞的進程將很難獲得鎖,從而進入饑餓狀態。
為了避免協程長時間無法獲取鎖,自1.8版本以來增加了一個狀態,即Mutex的Starving狀態。這個狀態下不會自旋,一旦有協程釋放鎖,那么一定會喚醒一個協程并成功加鎖。
## 5 Woken狀態
Woken狀態用于加鎖和解鎖過程的通信,舉個例子,同一時刻,兩個協程一個在加鎖,一個在解鎖,在加鎖的協程可能在自旋過程中,此時把Woken標記為1,用于通知解鎖協程不必釋放信號量了,好比在說:你只管解鎖好了,不必釋放信號量,我馬上就拿到鎖了。
## 6為什么重復解鎖要panic
可能你會想,為什么Go不能實現得更健壯些,多次執行Unlock()也不要panic?
仔細想想Unlock的邏輯就可以理解,這實際上很難做到。Unlock過程分為將Locked置為0,然后判斷Waiter值,如果值>0,則釋放信號量。
如果多次Unlock(),那么可能每次都釋放一個信號量,這樣會喚醒多個協程,多個協程喚醒后會繼續在Lock()的邏輯里搶鎖,勢必會增加Lock()實現的復雜度,也會引起不必要的協程切換。
- 概述
- go語言基礎特性
- Go語言聲明
- Go項目構建及編譯
- go command
- 程序設計原則
- Go基礎
- 變量
- 常量
- iota
- 基本類型
- byte和rune類型
- 類型定義和類型別名
- 數組
- string
- 高效字符串連接
- string底層原理
- 運算符
- new
- make
- 指針
- 下劃線 & import
- 語法糖
- 簡短變量申明
- 流程控制
- ifelse
- switch
- select
- select實現原理
- select常見案例
- for
- range
- range實現原理
- 常見案例
- range陷阱
- Goto&Break&Continue
- Go函數
- 函數
- 可變參數函數
- 高階函數
- init函數和main函數
- 匿名函數
- 閉包
- 常用內置函數
- defer
- defer常見案例
- defer規則
- defer與函數返回值
- defer實現原理
- defer陷阱
- 數據結構
- slice
- slice內存布局
- slice&array
- slice底層實現
- slice陷阱
- map
- Map實現原理
- 集合
- List
- Set
- 線程安全數據結構
- sync.Map
- Concurrent Map
- 面向對象編程
- struct
- 匿名結構體&匿名字段
- 嵌套結構體
- 結構體的“繼承”
- struct tag
- 行為方法
- 方法與函數
- type Method Value & Method Expressions
- interface
- 類型斷言
- 多態
- 錯誤機制
- error
- 自定義錯誤
- panic&recover
- reflect
- reflect包
- 應用示例
- DeepEqual
- 反射-fillObjectField
- 反射-copyObject
- IO
- 讀取文件
- 寫文件
- bufio
- ioutil
- Go網絡編程
- tcp
- tcp粘包
- udp
- HTTP
- http服務
- httprouter
- webSocket
- go并發編程
- Goroutine
- thread vs goroutine
- Goroutine任務取消
- 通過channel廣播實現
- Context
- Goroutine調度機制
- goroutine調度器1.0
- GMP模型調度器
- 調度器竊取策略
- 調度器的生命周期
- 調度過程全解析
- channel
- 無緩沖的通道
- 緩沖信道
- 單向信道
- chan實現原理
- 共享內存并發機制
- mutex互斥鎖
- mutex
- mutex原理
- mutex模式
- RWLock
- 使用信道處理競態條件
- WaitGroup
- 工作池
- 并發任務
- once運行一次
- 僅需任意任務完成
- 所有任務完成
- 對象池
- 定時器Timer
- Timer
- Timer實現原理
- 周期性定時器Ticker
- Ticker對外接口
- ticker使用場景
- ticker實現原理
- ticker使用陷阱
- 包和依賴管理
- package
- 依賴管理
- 測試
- 單元測試
- 表格測試法
- Banchmark
- BDD
- 常用架構模式
- Pipe-filter pattern
- Micro Kernel
- JSON
- json-內置解析器
- easyjson
- 性能分析
- gc
- 工具類
- fmt
- Time
- builtin
- unsafe
- sync.pool
- atomic
- flag
- runtime
- strconv
- template