golang 互斥鎖 讀寫鎖
一 為什麼加鎖?
Go是多執行緒的,存在多個goroutine同時操作一個資源(臨界區),這種情況會發生競態問題(資料競態)。類比火車上的衛生間被車廂裡的人競爭。
這裡注意一點:
go裡的鎖,並非說給火車的衛生間設定一個門鎖,而是給各個乘客發放一個對講機。乘客先詢問對講機能否使用衛生間,若通過則直接去,不通過則一直等待,知道對講機另一邊通過了。
所以這樣我們就可以理解為,倘若乘客x未被髮放對講機,那麼他要是用衛生間,則直接前去就好。
那麼這樣有個問題,當x使用衛生間的時候,其他有對講機並通過的乘客前往,這就造成了競態問題。如何解決?---------------------> 很明顯,不要遺漏給x發放對講機!
(當然,另一種解決方案就是設定一個門鎖,但是golang的機制不是這樣的,這點很關鍵)
二 sync.Mutex互斥鎖
互斥鎖能夠保證同一時間有且只有一個goroutine進入臨界區,其他的goroutine則在等待鎖;
當互斥鎖釋放後,等待的goroutine才可以獲取鎖進入臨界區,多個goroutine同時等待一個鎖時,喚醒的策略是隨機的。
(我一加鎖,其他人啥都別幹,等著吧)
func main(){ var lock sync.Mutex go func() { fmt.Println("乘客1詢問對講機能否使用衛生間 at"+ time.Now().String()) lock.Lock() defer lock.Unlock() fmt.Println("乘客1開始 at "+ time.Now().String()) time.Sleep(time.Second * 2) fmt.Println("乘客1結束 at "+ time.Now().String()) }() time.Sleep(time.Second/10) go func() { fmt.Println("乘客2詢問對講機能否使用衛生間 at"+ time.Now().String()) lock.Lock() defer lock.Unlock() fmt.Println("乘客2開始 at "+ time.Now().String()) time.Sleep(time.Second * 2) fmt.Println("乘客2結束 at "+ time.Now().String()) }() go func() { fmt.Println("乘客3(無對講機)直接使用 "+ time.Now().String()) time.Sleep(time.Second * 2) fmt.Println("乘客3(無對講機)結束 "+ time.Now().String()) }() //fmt.Println("main run "+ time.Now().String()) time.Sleep(time.Second * 15) }
我們看到 31s 的時候,1 2 3 同時想使用衛生間, 1先獲取到對講機的許可權,開始使用,2就只能等待,而3沒有對講機,那麼他就直接使用了。
33s的時候, 1 和 3 使用完畢,2得到了對講機的許可權,開始使用。
35s的時候,2 使用結束。
(要處理3和1同時使用的問題,就要給3也加個對講機,也就是程式碼中上個鎖)
三 sync.RWMutex讀寫互斥鎖
讀寫鎖分為兩種:讀鎖和寫鎖。
當一個goroutine獲取讀鎖之後,其他的goroutine如果是獲取讀鎖會繼續獲得鎖,如果是獲取寫鎖就會等待;
當一個goroutine獲取寫鎖之後,其他的goroutine無論是獲取讀鎖還是寫鎖都會等待。
- 讀鎖RLock() ,我開始讀了,你們也可以來讀,但是誰都別寫
- 寫鎖Lock() ,我要寫了,所有人注意,別做任何操作
func main(){ var rwLock sync.RWMutex fmt.Println("begin " + time.Now().String()) // 獲取寫鎖 for i := 0 ; i < 5; i++{ go func(i int) { rwLock.Lock() defer rwLock.Unlock() fmt.Println("write func " + strconv.Itoa(i) +" get wlock at " + time.Now().String()) time.Sleep(time.Second) }(i) }// 獲取讀鎖 for i := 0 ; i < 5 ;i ++{ go func(i int) { rwLock.RLock() defer rwLock.RUnlock() fmt.Println("read func " + strconv.Itoa(i) +" get rlock at " + time.Now().String()) time.Sleep(time.Second) }(i) } //散人 for i := 0 ; i < 5 ;i ++{ go func(i int) { fmt.Println("nolocker func " + strconv.Itoa(i) +" get rlock at " + time.Now().String()) time.Sleep(time.Second) }(i) } // 保證所有的 goroutine 執行結束 time.Sleep(time.Second * 10) }
我們看到 58s 的時候,write0 獲取了寫鎖(其他人啥事別幹),此時read動彈不了,不過散人nolocker那五位牛人可以把事情做完(因為他沒有對講機)
59s的時候,write0 的事情幹完了,對講機通知read0開始幹活,1幹活的時候上了讀鎖(其他reader可以幹活,writer就別動彈了),所以59s的時候,read0~read4開始幹活
60s時,read0~read4全都幹完活,釋放了他們的讀鎖,那麼write鎖可以動彈了,對講機隨機喊來了write1來幹活,write1加了寫鎖,直到幹完活釋放
61s也就是1s的時候write2開始幹活,依次執行,所有write幹完
本篇到此結束!!!