在本教程中,咱們將瞭解互斥鎖。咱們還將學習如何使用互斥鎖和通道解決競爭問題。編程
臨界區
在講互斥鎖以前,瞭解併發編程中臨界區的概念很是重要。當程序同時運行時,多個 goroutine 同時訪問修改共享資源,修改共享資源的這段代碼稱爲臨界區。例如,假設咱們要將變量 x 遞增1.併發
x = x + 1
只要上面的代碼被一個 goroutine 訪問,就不會有任何問題。函數
<!-- more -->工具
讓咱們看看爲何當有多個 goroutine 同時運行時,這段代碼會失敗。爲簡單起見,咱們假設有2個 goroutine 同時運行上面的代碼行。學習
在內部,上面的代碼行將由系統按下面的步驟執行。spa
- 獲取 x 的當前值
- 計算 x+1
- 將步驟2中計算的值分配給 x
當這三個步驟僅由一個 goroutine 進行時,一切都很順利。code
讓咱們討論當2個 goroutine 同時運行此代碼會發生什麼。下圖描繪了兩個 goroutine 同時訪問代碼行時可能發生的狀況。cdn
x+1x+1
讓咱們再看看可能發生的不一樣狀況:blog
在上面的場景中,協程1開始執行並完成3個步驟,這時x值是1,而後開始執行協程2,如今x的值已是1了,在協程2執行完成,x的值就是2了。
所以,在這兩種狀況下,你能夠看到 x 的最終值是1或2取決於上下文切換的方式。這種類型的不良狀況,其中程序的輸出取決於 goroutine 的執行順序,稱爲競爭條件。
爲了不競爭條件,能夠經過使用 Mutex 實現。
Mutex 互斥
Mutex 用於提供鎖定機制,以確保在任什麼時候間點只有一個 goroutine 在臨界區運行,已防止發生競爭條件。
syncLockUnlockLockUnlock
mutex.Lock() x = x + 1 mutex.Unlock()
x=x+1
LockLock
有競爭條件的程序
咱們將編寫一個具備競爭條件的程序,在接下來的部分中咱們將修復競爭條件。
package main import ( "fmt" "sync" ) var x = 0 func increment(wg *sync.WaitGroup) { x = x + 1 wg.Done() } func main() { var w sync.WaitGroup for i := 0; i < 1000; i++ { w.Add(1) go increment(&w) } w.Wait() fmt.Println("final value of x", x) }
final value of x 941final value of x 928final value of x 922
使用互斥鎖解決競爭條件
在上面的程序中,咱們產生了1000個Goroutines。若是每一個都將x的值遞增1,則x的最終指望值應爲1000.在本節中,咱們將使用互斥鎖修復上述程序中的競爭條件。
package main import ( "fmt" "sync" ) var x = 0 func increment(wg *sync.WaitGroup, m *sync.Mutex) { m.Lock() x = x + 1 m.Unlock() wg.Done() } func main() { var w sync.WaitGroup var m sync.Mutex for i := 0; i < 1000; i++ { w.Add(1) go increment(&w, &m) } w.Wait() fmt.Println("final value of x", x) }
incrementx = x + 1m.Lock()m.Unlock()
使用 channel 解決競爭條件
咱們也可使用通道解決競爭條件。讓咱們看看如何實現的。
package main import ( "fmt" "sync" ) var x = 0 func increment(wg *sync.WaitGroup, ch chan bool) { ch <- true x = x + 1 <- ch wg.Done() } func main() { var w sync.WaitGroup ch := make(chan bool, 1) for i := 0; i < 1000; i++ { w.Add(1) go increment(&w, ch) } w.Wait() fmt.Println("final value of x", x) }
increment
這個程序也打印
final value of x 1000
Mutex vs Channel
咱們使用互斥鎖和通道解決了競爭條件問題。那麼咱們如何決定什麼時候使用呢?答案在於你要解決的問題。若是你解決的問題更合適互斥鎖,那麼請繼續使用互斥鎖。若是須要,請不要猶豫使用互斥鎖。若是問題更適合通道,那麼使用它:)(沒有銀彈)
大多數 Go 新手嘗試使用通道解決每一個併發問題,由於它是該語言的一個很酷的功能。這是錯誤的,語言爲咱們提供了使用 Mutex 和 Channel 的選擇,而且選擇任何一種都沒有錯。
通常狀況下,當 goroutine 須要互相通訊時使用通道,當只有一個 goroutine 應該訪問代碼的臨界區時使用互斥。
在咱們上面的問題狀況下,我寧願使用互斥鎖,由於這個問題不須要 goroutine 之間任何通訊。所以互斥鎖是一種天然的選擇。
個人建議是根據問題選擇工具,不要試圖讓問題適應工具。