Golang學習筆記之互斥鎖(Mutex)
Go語言包中的sync包提供了兩種鎖,互斥鎖(sync.Mutex)和讀寫鎖(sync.RWMutex)
這一篇博文我們只說一下互斥鎖。
Mutex是一個互斥鎖,可以建立為其他結構體的欄位;零值為解鎖狀態。Mutex型別的鎖和執行緒無關,可以由不同的執行緒加鎖和解鎖。
• 它只有兩個公開方法:Lock()加鎖,unlock()解鎖。
• 在同一個協程中加鎖後,不能再繼續對其加鎖,否則會panic。只有在解鎖之後才能再次加鎖。
• 只允許只有一個讀或者寫的場景
• 在使用Unlock()解鎖前,未使用Lock()加鎖,就會引起一個執行錯誤。
函式原型
func (m *Mutex) Lock()
Lock方法鎖住m,如果m已經加鎖,則阻塞直到m解鎖。
func (m *Mutex) Unlock()
Unlock方法解鎖m,如果m未加鎖會導致執行時錯誤。鎖和執行緒無關,可以由不同的執行緒加鎖和解鎖。
一個例子理解互斥鎖的作用
package main import ( "fmt" "sync" ) var num = 0 func increment(wg *sync.WaitGroup) { num = num + 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("num =", num) }
在上述程式中,呼叫1000個協程來進行num=num+1操作
執行幾次的輸出分別為
num = 971
num = 944
num = 959
每次執行都沒有達到預期的效果,因為多個併發的協程試圖訪問 num 的值,這時就會發生競態條件。
現在我們可以對上述程式加上鎖,每次只能由一個執行緒來操作num的值
package main import ( "fmt" "sync" ) var num = 0 func increment(wg *sync.WaitGroup, m *sync.Mutex) { //互斥鎖 m.Lock()//當有執行緒進去進行加鎖 num = num + 1 m.Unlock()//出來後解鎖,其他執行緒才可以進去 wg.Done() } var w sync.WaitGroup var m sync.Mutex for i := 0; i < 1000; i++ { w.Add(1) go increment(&w, &m)//這裡要傳引用並不能傳值,如果傳值,那麼每個協程都會得到 Mutex 的一份拷貝,競態條件還是會發生。 } w.Wait() fmt.Println("num =", num)
輸出
num = 1000
我們也可以使用緩衝通道來實現互斥鎖
func increment2(wg *sync.WaitGroup, b chan bool) { //自定義互斥鎖 b <- true num = num + 1 <-b wg.Done() } func main() { var w sync.WaitGroup ch := make(chan bool,1) for i := 0; i < 1000; i++ { w.Add(1) go increment2(&w, ch) } w.Wait() fmt.Println("num =", num) }
輸出
num = 1000
func main() { wa := sync.WaitGroup{} var mu sync.Mutex fmt.Println("加鎖0") mu.Lock() fmt.Printf("上鎖中0\t") for i := 1; i < 4; i++ { wa.Add(1) go func(i int) { fmt.Printf("加鎖%d\t", i) mu.Lock() fmt.Printf("上鎖中%d\t", i) time.Sleep(time.Second * 1) mu.Unlock() fmt.Printf("解鎖%d\t", i) wa.Done() }(i) } time.Sleep(time.Second * 5) mu.Unlock() fmt.Println("\n解鎖0") wa.Wait() }
輸出為
加鎖0
上鎖中0加鎖2 加鎖3 加鎖1 上鎖中2
解鎖0
解鎖2 上鎖中3解鎖3 上鎖中1解鎖1