Golang WaitGroup原始碼分析
針對 Golang 1.9 的 sync.WaitGroup 進行分析,與 Golang 1.10 基本一樣除了將panic
改為了throw
之外其他的都一樣。 原始碼位置:sync\waitgroup.go
。
結構體
type WaitGroup struct {
noCopy noCopy // noCopy可以嵌入到結構中,在第一次使用後不可複製,使用go vet作為檢測使用
// 位值:高32位是計數器,低32位是goroution等待計數。
// 64位的原子操作需要64位的對齊,但是32位。編譯器不能確保它,所以分配了12個byte對齊的8個byte作為狀態。
state1 [12]byte // byte=uint8範圍:0~255,只取前8個元素。轉為2進位制:0000 0000,0000 0000... ...0000 0000
sema uint32 // 訊號量,用於喚醒goroution
}
不知道大家是否和我一樣,不論是使用 Java 的 CountDownLatch 還是 Golang 的 WaitGroup,都會疑問,可以裝下多個執行緒 | 協程等待呢?看了原始碼後可以回答了,可以裝下
1111 1111 1111 ... 1111
\________32___________/
2^32 個辣麼多!所以不需要擔心單機情況下會被撐爆了。
函式
以下程式碼已經去掉了與核心程式碼無關的 race 程式碼。
Add
新增或者減少等待 goroutine 的數量。
新增的 delta,可能是負的,到 WaitGroup 計數器。
- 如果計數器變為零,所有被阻塞的 goroutines 都會被釋放。
- 如果計數器變成負數,就增加恐慌。
func (wg *WaitGroup) Add(delta int) {
// 獲取到wg.state1陣列中元素組成的二進位制對應的十進位制的值
statep := wg.state()
// 高32位是計數器
state := atomic.AddUint64(statep, uint64(delta)<<32)
// 獲取計數器
v := int32(state >> 32)
w := uint32(state)
// 計數器為負數,報panic
if v < 0 {
panic("sync: negative WaitGroup counter")
}
// 新增與等待併發呼叫,報panic
if w != 0 && delta > 0 && v == int32(delta) {
panic("sync: WaitGroup misuse: Add called concurrently with Wait")
}
// 計數器新增成功
if v > 0 || w == 0 {
return
}
// 當等待計數器> 0時,而goroutine設定為0。
// 此時不可能有同時發生的狀態突變:
// - 增加不能與等待同時發生,
// - 如果計數器counter == 0,不再增加等待計數器
if *statep != state {
panic("sync: WaitGroup misuse: Add called concurrently with Wait")
}
// Reset waiters count to 0.
*statep = 0
for ; w != 0; w-- {
// 目的是作為一個簡單的wakeup原語,以供同步使用。true為喚醒排在等待佇列的第一個goroutine
runtime_Semrelease(&wg.sema, false)
}
}
// unsafe.Pointer其實就是類似C的void *,在golang中是用於各種指標相互轉換的橋樑。
// uintptr是golang的內建型別,是能儲存指標的整型,uintptr的底層型別是int,它和unsafe.Pointer可相互轉換。
// uintptr和unsafe.Pointer的區別就是:unsafe.Pointer只是單純的通用指標型別,用於轉換不同型別指標,它不可以參與指標運算;
// 而uintptr是用於指標運算的,GC 不把 uintptr 當指標,也就是說 uintptr 無法持有物件,uintptr型別的目標會被回收。
// state()函式可以獲取到wg.state1陣列中元素組成的二進位制對應的十進位制的值
func (wg *WaitGroup) state() *uint64 {
if uintptr(unsafe.Pointer(&wg.state1))%8 == 0 {
return (*uint64)(unsafe.Pointer(&wg.state1))
} else {
return (*uint64)(unsafe.Pointer(&wg.state1[4]))
}
}
Done
相當於 Add(-1)。
func (wg *WaitGroup) Done() {
// 計數器減一
wg.Add(-1)
}
Wait
執行阻塞,直到所有的 WaitGroup 數量變成 0。
func (wg *WaitGroup) Wait() {
// 獲取到wg.state1陣列中元素組成的二進位制對應的十進位制的值
statep := wg.state()
// cas演算法
for {
state := atomic.LoadUint64(statep)
// 高32位是計數器
v := int32(state >> 32)
w := uint32(state)
// 計數器為0,結束等待
if v == 0 {
// Counter is 0, no need to wait.
return
}
// 增加等待goroution計數,對低32位加1,不需要移位
if atomic.CompareAndSwapUint64(statep, state, state+1) {
// 目的是作為一個簡單的sleep原語,以供同步使用
runtime_Semacquire(&wg.sema)
if *statep != 0 {
panic("sync: WaitGroup is reused before previous Wait has returned")
}
return
}
}
}
使用注意事項
- WaitGroup 不能保證多個 goroutine 執行次序
- WaitGroup 無法指定固定的 goroutine 數目
更多原創文章乾貨分享,請關注公眾號
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- Go的WaitGroup原始碼分析GoAI原始碼
- Golang WaitGroup 底層原理及原始碼詳解GolangAI原始碼
- go中waitGroup原始碼解讀GoAI原始碼
- Golang1.7 Goroutine原始碼分析Golang原始碼
- golang開發:CSP-WaitGroup MutexGolangAIMutex
- Golang Sync.WaitGroup 使用及原理GolangAI
- 最清晰易懂的 Go WaitGroup 原始碼剖析GoAI原始碼
- golang 原始碼分析之scheduler排程器Golang原始碼
- golang RWMutex讀寫互斥鎖原始碼分析GolangMutex原始碼
- golang語言非同步通訊之WaitGroupGolang非同步AI
- golang 利用 WaitGroup 控制多個 goroutine 同時完成GolangAI
- redis個人原始碼分析1----hyperloglog(golang實現)Redis原始碼Golang
- Retrofit原始碼分析三 原始碼分析原始碼
- 6. 開篇《 刻意學習 Golang - 標準庫原始碼分析 》Golang原始碼
- 集合原始碼分析[2]-AbstractList 原始碼分析原始碼
- 集合原始碼分析[1]-Collection 原始碼分析原始碼
- 集合原始碼分析[3]-ArrayList 原始碼分析原始碼
- Guava 原始碼分析之 EventBus 原始碼分析Guava原始碼
- 搭建sonarqube分析golang程式碼Golang
- golang http/transport 程式碼分析GolangHTTP
- Golang slice 從原始碼來理解Golang原始碼
- 介紹 golang net/http 原始碼GolangHTTP原始碼
- Android 原始碼分析之 AsyncTask 原始碼分析Android原始碼
- 【JDK原始碼分析系列】ArrayBlockingQueue原始碼分析JDK原始碼BloC
- 以太坊原始碼分析(36)ethdb原始碼分析原始碼
- 以太坊原始碼分析(38)event原始碼分析原始碼
- 以太坊原始碼分析(41)hashimoto原始碼分析原始碼
- 以太坊原始碼分析(43)node原始碼分析原始碼
- 以太坊原始碼分析(52)trie原始碼分析原始碼
- 不得不知道的Golang之sync.Map原始碼分析Golang原始碼
- 深度 Mybatis 3 原始碼分析(一)SqlSessionFactoryBuilder原始碼分析MyBatis原始碼SQLSessionUI
- 以太坊原始碼分析(51)rpc原始碼分析原始碼RPC
- 原始碼剖析 golang 中 sync.Mutex原始碼GolangMutex
- golang原始碼分析:sync.Pool 如何從讀寫加鎖到無鎖Golang原始碼
- 【Android原始碼】Fragment 原始碼分析Android原始碼Fragment
- 【Android原始碼】Intent 原始碼分析Android原始碼Intent
- k8s client-go原始碼分析 informer原始碼分析(6)-Indexer原始碼分析K8SclientGo原始碼ORMIndex
- k8s client-go原始碼分析 informer原始碼分析(4)-DeltaFIFO原始碼分析K8SclientGo原始碼ORM