28 學習 Go 協程 互斥鎖和讀寫鎖

2021-10-06 20:22:38 字數 4535 閱讀 4337

在 「19. 學習 go 協程:詳解通道/通道」這一節裡我詳細地介紹信道的一些用法,要知道的是在 go 語言中,通道的地位非常高,它是 first class 級別的,面對併發問題,我們始終應該優先考慮使用通道,如果通過通道解決不了的,不得不使用共享記憶體來實現併發程式設計的,那 golang 中的鎖機制,就是你繞不過的知識點了。

今天就來講一講 golang 中的鎖機制。

在 golang 裡有專門的方法來實現鎖,還是上一節裡介紹的 sync 包。

這個包有兩個很重要的鎖型別

乙個叫mutex, 利用它可以實現互斥鎖。

乙個叫rwmutex,利用它可以實現讀寫鎖。

使用互斥鎖(mutex,全稱 mutual exclusion)是為了來保護乙個資源不會因為併發操作而引起衝突導致資料不準確。

舉個例子,就像下面這段**,我開啟了三個協程,每個協程分別往 count 這個變數加1000次 1,理論上看,最終的 count 值應試為 3000

package main

import (

"fmt"

"sync"

)func add(count *int, wg *sync.waitgroup)

wg.done()

}func main()

可執行多次的結果,都不相同

// 第一次

count 的值為: 2854

// 第二次

count 的值為: 2673

// 第三次

count 的值為: 2840

原因就在於這三個協程在執行時,先讀取 count 再更新 count 的值,而這個過程並不具備原子性,所以導致了資料的不準確。

解決這個問題的方法,就是給 add 這個函式加上 mutex 互斥鎖,要求同一時刻,僅能有乙個協程能對 count 操作。

在寫**前,先了解一下 mutex 鎖的兩種定義方法

// 第一種

var lock *sync.mutex

lock = new(sync.mutex)

// 第二種

lock := &sync.mutex{}

然後就可以修改你上面的**,如下所示

import (

"fmt"

"sync"

)func add(count *int, wg *sync.waitgroup, lock *sync.mutex)

wg.done()

}func main()

count := 0

wg.add(3)

go add(&count, &wg, lock)

go add(&count, &wg, lock)

go add(&count, &wg, lock)

wg.wait()

fmt.println("count 的值為:", count)

}

此時,不管你執行多少次,輸出都只有乙個結果

count 的值為: 3000
使用 mutext 鎖雖然很簡單,但仍然有幾點需要注意:

mutex 是最簡單的一種鎖型別,他提供了乙個傻瓜式的操作,加鎖解鎖加鎖解鎖,讓你不需要再考慮其他的。

簡單同時意味著在某些特殊情況下有可能會造成時間上的浪費,導致程式效能低下。

舉個例子,我們平時去圖書館,要嘛是去借書,要嘛去還書,借書的流程繁鎖,沒有辦卡的還要讓管理員給你辦卡,因此借書通常都要排老長的隊,假設圖書館裡只有乙個管理員,按照 mutex(互斥鎖)的思想, 這個管理員同一時刻只能服務乙個人,這就意味著,還書的也要跟借書的一起排隊。

可還書的步驟非常簡單,可能就把書給管理員掃下碼就可以走了。

如果讓還書的人,跟借書的人一起排隊,那估計有很多人都不樂意了。

因此,圖書館為了提高整個流程的效率,就允許還書的人,不需要排隊,可以直接自助還書。

圖書管將館裡的人分得更細了,對於讀者的不同需求提供了不同的方案。提高了效率。

rwmutex,也是如此,它將程式對資源的訪問分為讀操作和寫操作

理解了這個後,再來看看,如何使用 rwmutex?

定義乙個 rwmuteux 鎖,有兩種方法

// 第一種

var lock *sync.rwmutex

lock = new(sync.rwmutex)

// 第二種

lock := &sync.rwmutex{}

rwmutex 裡提供了兩種鎖,每種鎖分別對應兩個方法,為了避免死鎖,兩個方法應成對出現,必要時請使用 defer。

接下來,直接看一下例子吧

package main

import (

"fmt"

"sync"

"time"

)func main()

lock.lock()

for i := 0; i < 4; i++ (i)

}time.sleep(time.second * 2)

fmt.println("準備釋放寫鎖,讀鎖不再阻塞")

// 寫鎖一釋放,讀鎖就自由了

lock.unlock()

// 由於會等到讀鎖全部釋放,才能獲得寫鎖

// 因為這裡一定會在上面 4 個協程全部完成才能往下走

lock.lock()

fmt.println("程式退出...")

lock.unlock()

}

輸出如下

第 1 個協程準備開始... 

第 0 個協程準備開始...

第 3 個協程準備開始...

第 2 個協程準備開始...

準備釋放寫鎖,讀鎖不再阻塞

第 2 個協程獲得讀鎖, sleep 1s 後,釋放鎖

第 3 個協程獲得讀鎖, sleep 1s 後,釋放鎖

第 1 個協程獲得讀鎖, sleep 1s 後,釋放鎖

第 0 個協程獲得讀鎖, sleep 1s 後,釋放鎖

程式退出...

01. 開發環境的搭建(goland & vs code)02. 學習五種變數建立的方法03. 詳解資料型別:**整形與浮點型**

04. 詳解資料型別:byte、rune與string

05. 詳解資料型別:陣列與切片

06. 詳解資料型別:字典與布林型別

07. 詳解資料型別:指標

08. 物件導向程式設計:結構體與繼承

09. 一篇文章理解 go 裡的函式

10. go語言流程控制:if-else 條件語句

11. go語言流程控制:switch-case 選擇語句

12. go語言流程控制:for 迴圈語句

13. go語言流程控制:goto 無條件跳轉

14. go語言流程控制:defer 延遲呼叫

15. 物件導向程式設計:介面與多型

16. 關鍵字:make 和 new 的區別?

17. 一篇文章理解 go 裡的語句塊與作用域

18. 學習 go 協程:goroutine

19. 學習 go 協程:詳解通道/通道

20. 幾個通道死鎖經典錯誤案例詳解

21. 學習 go 協程:waitgroup

22. 學習 go 協程:互斥鎖和讀寫鎖

23. go 裡的異常處理:panic 和 recover

24. 超詳細解讀 go modules 前世今生及入門使用

25. go 語言中關於包匯入必學的 8 個知識點

26. 如何開源自己寫的模組給別人用?

27. 說說 go 語言中的型別斷言?

28. 這五點帶你理解go語言的select用法

Go 學習筆記11 互斥鎖,讀寫鎖,狀態協程

互斥所 它由標準庫sync.mutex來表示 方法有 lock和unlock package main import fmt time func printer str string fmt.println func print1 func print2 func main 輸出結果為 hwoelr...

Go 互斥鎖和讀寫互斥鎖的實現

目錄 先來看這樣一段 所存在的問題 var wg sync.waitgroup var x int64 func main func f wg.done 這裡為什麼輸出是 12135 不同的機器結果不一樣 而不是20000。因為 x 的賦值,總共分為三個步驟 取出x的值 計算x的結果 給x賦值。那麼...

互斥鎖和讀寫鎖

互斥鎖的型別 對資源的訪問是互斥的,即執行緒a對資源加鎖後,在a解鎖前,其他執行緒不能訪問這個加鎖的資源。互斥鎖的特點 多個執行緒訪問資源的時候是序列的 互斥鎖的使用步驟 建立乙個互斥鎖 pthread mutex t mutex 初始化這把鎖 pthread mutex init mutex,nu...