在本教程中,咱們將瞭解互斥鎖。咱們還將學習如何使用互斥鎖和通道解決競爭問題。編程

臨界區

在講互斥鎖以前,瞭解併發編程中臨界區的概念很是重要。當程序同時運行時,多個 goroutine 同時訪問修改共享資源,修改共享資源的這段代碼稱爲臨界區。例如,假設咱們要將變量 x 遞增1.併發

x = x + 1

只要上面的代碼被一個 goroutine 訪問,就不會有任何問題。函數

<!-- more -->工具

讓咱們看看爲何當有多個 goroutine 同時運行時,這段代碼會失敗。爲簡單起見,咱們假設有2個 goroutine 同時運行上面的代碼行。學習

在內部,上面的代碼行將由系統按下面的步驟執行。spa

  1. 獲取 x 的當前值
  2. 計算 x+1
  3. 將步驟2中計算的值分配給 x

當這三個步驟僅由一個 goroutine 進行時,一切都很順利。code

讓咱們討論當2個 goroutine 同時運行此代碼會發生什麼。下圖描繪了兩個 goroutine 同時訪問代碼行時可能發生的狀況。cdn

cs5

x+1x+1

讓咱們再看看可能發生的不一樣狀況:blog

cs-6

在上面的場景中,協程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 之間任何通訊。所以互斥鎖是一種天然的選擇。

個人建議是根據問題選擇工具,不要試圖讓問題適應工具。