Golang 推薦通過 channel 進行通訊和同步,但在實際開發中 sync 包用得也非常的多,在以太坊的源碼中也有很多這類應用的體現。
Go sync包提供了:sync.Mutex,sync.RMutex,sync.Once,sync.Cond,sync.Waitgroup,sync.atomic等,文本主要對sync.Mutex,sync.RMutex和sync.Waitgroup的使用進行了說明,後續會推出其它方法的使用說明。
sync包含一個 Locker interface:
type Locker interface { Lock() Unlock()}
該介面只有兩個方法,Lock() 和 Unlock()。整個sync包都是圍繞該介面實現。
互斥鎖Mutex
互斥鎖Mutex是Locker的一種具體實現,有兩個方法:
func (m *Mutex) Lock()func (m *Mutex) Unlock()
一個互斥鎖只能同時被一個 goroutine 鎖定,其它 goroutine 將阻塞直到互斥鎖被解鎖,之後再重新爭搶對互斥鎖的鎖定。
對一個未鎖定的互斥鎖解鎖將會產生執行階段錯誤。
package mainimport ( "fmt" "sync" "time")func main() { ch := make(chan struct{}, 2) var l sync.Mutex go func() { l.Lock() defer l.Unlock() fmt.Println("goroutine1: 鎖定大概 2s") time.Sleep(time.Second * 2) fmt.Println("goroutine1: 解鎖了,可以開搶了!") ch <- struct{}{} }() go func() { fmt.Println("groutine2: 等待解鎖") l.Lock() defer l.Unlock() fmt.Println("goroutine2: 我鎖定了") ch <- struct{}{} }() // 等待 goroutine 執行結束 for i := 0; i < 2; i++ { <-ch }}
讀寫鎖 RWMutex
讀寫鎖是針對讀寫操作的互斥鎖,讀寫鎖與互斥鎖最大的不同就是可以分別對 讀、寫 進行鎖定。一般用在大量讀操作、少量寫操作的情況:
func (rw *RWMutex) Lock()func (rw *RWMutex) Unlock()func (rw *RWMutex) RLock()func (rw *RWMutex) RUnlock()
由於這裡需要區分讀寫鎖定,讀寫鎖這樣定義:
讀鎖定(RLock),對讀操作進行鎖定
讀解鎖(RUnlock),對讀鎖定進行解鎖
寫鎖定(Lock),對寫操作進行鎖定
寫解鎖(Unlock),對寫鎖定進行解鎖
不要混用鎖定和解鎖,如:Lock 和 RUnlock、RLock 和 Unlock。因為對未讀鎖定的讀寫鎖進行讀解鎖或對未寫鎖定的讀寫鎖進行寫解鎖將會引起執行階段錯誤。
如何理解讀寫鎖呢?
也就是說,當有一個 goroutine 獲得寫鎖定,其它無論是讀鎖定還是寫鎖定都將阻塞直到寫解鎖;當有一個 goroutine 獲得讀鎖定,其它讀鎖定仍然可以繼續;當有一個或任意多個讀鎖定,寫鎖定將等待所有讀鎖定解鎖之後才能夠進行寫鎖定。所以說這裡的讀鎖定(RLock)目的其實是告訴寫鎖定:有很多人正在讀取資料,需等它們讀(讀解鎖)完再來寫(寫鎖定)。
package mainimport ( "fmt" "math/rand" "sync")var count intvar rw sync.RWMutexfunc main() { ch := make(chan struct{}, 10) for i := 0; i < 5; i++ { go read(i, ch) } for i := 0; i < 5; i++ { go write(i, ch) } for i := 0; i < 10; i++ { <-ch }}func read(n int, ch chan struct{}) { rw.RLock() fmt.Printf("goroutine %d 進入讀操作...\n", n) v := count fmt.Printf("goroutine %d 讀取結束,值為:%d\n", n, v) rw.RUnlock() ch <- struct{}{}}func write(n int, ch chan struct{}) { rw.Lock() fmt.Printf("goroutine %d 進入寫操作...\n", n) v := rand.Intn(1000) count = v fmt.Printf("goroutine %d 寫入結束,新值為:%d\n", n, v) rw.Unlock() ch <- struct{}{}}
WaitGroup
WaitGroup 用於等待一組 goroutine 結束,用法比較簡單。它有三個方法:
func (wg *WaitGroup) Add(delta int)func (wg *WaitGroup) Done()func (wg *WaitGroup) Wait()
Add 用來添加 goroutine 的個數。Done 執行一次數量減 1。Wait 用來等待結束:
package mainimport ( "fmt" "sync")func main() { var wg sync.WaitGroup for i, s := range seconds { // 計數加 1 wg.Add(1) go func(i, s int) { // 計數減 1 defer wg.Done() fmt.Printf("goroutine%d 結束\n", i) }(i, s) } // 等待執行結束 wg.Wait() fmt.Println("所有 goroutine 執行結束")}
注意,wg.Add() 方法一定要在 goroutine 開始前執行。