Go中使用sync.Mutex類型實現mutex(排他鎖、互斥鎖)。在源代碼的sync/mutex.go文件中,有以下定義:web
// A Mutex is a mutual exclusion lock. // The zero value for a Mutex is an unlocked mutex. // // A Mutex must not be copied after first use. type Mutex struct { state int32 sema uint32 }
這沒有任何非凡的地方。和mutex相關的全部事情都是經過sync.Mutex類型的兩個方法sync.Lock()和sync.Unlock()函數來完成的,前者用於獲取sync.Mutex鎖,後者用於釋放sync.Mutex鎖。sync.Mutex一旦被鎖住,其它的Lock()操做就沒法再獲取它的鎖,只有經過Unlock()釋放鎖以後才能經過Lock()繼續獲取鎖。安全
也就是說,已有的鎖會致使其它申請Lock()操做的goroutine被阻塞,且只有在Unlock()的時候纔會解除阻塞。併發
另外須要注意,sync.Mutex不區分讀寫鎖,只有Lock()與Lock()之間纔會致使阻塞的狀況,若是在一個地方Lock(),在另外一個地方不Lock()而是直接修改或訪問共享數據,這對於sync.Mutex類型來講是容許的,由於mutex不會和goroutine進行關聯。若是想要區分讀、寫鎖,可使用sync.RWMutex類型,見後文。函數
在Lock()和Unlock()之間的代碼段稱爲資源的臨界區(critical section),在這一區間內的代碼是嚴格被Lock()保護的,是線程安全的,任何一個時間點都只能有一個goroutine執行這段區間的代碼。ui
如下是使用sync.Mutex的一個示例,稍後是很是詳細的分析過程。spa
package main import ( "fmt" "sync" "time" ) // 共享變量 var ( m sync.Mutex v1 int ) // 修改共享變量 // 在Lock()和Unlock()之間的代碼部分是臨界區 func change(i int) { m.Lock() time.Sleep(time.Second) v1 = v1 + 1 if v1%10 == 0 { v1 = v1 - 10*i } m.Unlock() } // 訪問共享變量 // 在Lock()和Unlock()之間的代碼部分是是臨界區 func read() int { m.Lock() a := v1 m.Unlock() return a } func main() { var numGR = 21 var wg sync.WaitGroup fmt.Printf("%d", read()) // 循環建立numGR個goroutine // 每一個goroutine都執行change()、read() // 每一個change()和read()都會持有鎖 for i := 0; i < numGR; i++ { wg.Add(1) go func(i int) { defer wg.Done() change(i) fmt.Printf(" -> %d", read()) }(i) } wg.Wait() }
第一次執行結果:線程
0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> -100 -> -99 -> -98 -> -97 -> -96 -> -95 -> -94 -> -93 -> -92 -> -91 -> -260 -> -259
第二次執行結果:注意其中的-74和-72之間跨了一個數code
0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> -80 -> -79 -> -78 -> -77 -> -76 -> -75 -> -74 -> -72 -> -71 -> -230 -> -229 -> -229
上面的示例中,change()、read()都會申請鎖,並在準備執行完函數時釋放鎖,它們如何修改數據、訪問數據本文很少作解釋。須要詳細解釋的是main()中的for循環部分。資源
在for循環中,會不斷激活新的goroutine(共21個)執行匿名函數,在每一個匿名函數中都會執行change()和read(),意味着每一個goroutine都會申請兩次鎖、釋放兩次鎖,且for循環中沒有任何Sleep延遲,這21個goroutine幾乎是一瞬間同時激活的。it
但因爲change()和read()中都申請鎖,對於這21個goroutine將要分別執行的42個critical section,Lock()保證了在某一時間點只有其中一個goroutine能訪問其中一個critical section。當釋放了一個critical section,其它的Lock()將爭奪互斥鎖,也就是所謂的競爭現象(race condition)。由於競爭的存在,這42個critical section被訪問的順序是隨機的,徹底沒法保證哪一個critical section先被訪問。
對於前9個被調度到的goroutine,不管是哪一個goroutine取得這9個change(i)中的critical section,都只是對共享變量v1作加1運算,但當第10個goroutine被調度時,因爲v1加1以後獲得10,它知足if條件,會執行v1 = v1 - i*10
,但這個i多是任意0到numGR之間的值(由於沒法保證併發的goroutine的調度順序),這使得v1的值從第10個goroutine開始出現隨機性。但從第10到第19個goroutine被調度的過程當中,也只是對共享變量v1作加1運算,這些值是能夠根據第10個數推斷出來的,到第20個goroutine,又再次隨機。依此類推。
此外,每一個goroutine中的read()也都會參與鎖競爭,因此並不能保證每次change(i)以後會隨之執行到read(),可能goroutine 1的change()執行完後,會跳轉到goroutine 3的change()上,這樣一來,goroutine 1的read()就沒法讀取到goroutine 1所修改的v1值,而是訪問到其它goroutine中修改後的值。因此,前面的第二次執行結果中出現了一次數據跨越。只不過執行完change()後當即執行read()的概率比較大,因此多數時候輸出的數據都是連續的。
總而言之,Mutex保證了每一個critical section安全,某一時間點只有一個goroutine訪問到這部分,但也所以而出現了隨機性。
若是Lock()後忘記了Unlock(),將會永久阻塞而出現死鎖。若是
其實,對於內置類型的共享變量來講,使用sync.Mutex和Lock()、Unlock()來保護也是不合理的,由於它們自身不包含Mute