GO語言并發(fā)之好用的sync包詳解
sync.Map 并發(fā)安全的Map
反例如下,兩個(gè)Goroutine
分別讀寫。
func unsafeMap(){ var wg sync.WaitGroup m := make(map[int]int) wg.Add(2) go func() { defer wg.Done() for i := 0; i < 10000; i++ { m[i] = i } }() go func() { defer wg.Done() for i := 0; i < 10000; i++ { fmt.Println(m[i]) } }() wg.Wait() }
執(zhí)行報(bào)錯(cuò):
0
fatal error: concurrent map read and map write
goroutine 7 [running]:
runtime.throw({0x10a76fa, 0x0})
......
使用并發(fā)安全的Map
func safeMap() { var wg sync.WaitGroup var m sync.Map wg.Add(2) go func() { defer wg.Done() for i := 0; i < 10000; i++ { m.Store(i, i) } }() go func() { defer wg.Done() for i := 0; i < 10000; i++ { fmt.Println(m.Load(i)) } }() wg.Wait() }
- 不需要
make
就能使用 - 還內(nèi)置了
Store
、Load
、LoadOrStore
、Delete
、Range
等操作方法,自行體驗(yàn)。
sync.Once 只執(zhí)行一次
很多場(chǎng)景下我們需要確保某些操作在高并發(fā)的場(chǎng)景下只執(zhí)行一次,例如只加載一次配置文件、只關(guān)閉一次通道等。
init
函數(shù)是當(dāng)所在的 package
首次被加載時(shí)執(zhí)行,若遲遲未被使用,則既浪費(fèi)了內(nèi)存,又延長(zhǎng)了程序加載時(shí)間。
sync.Once
可以在代碼的任意位置初始化和調(diào)用,因此可以延遲到使用時(shí)再執(zhí)行,并發(fā)場(chǎng)景下是線程安全的。
在多數(shù)情況下,sync.Once
被用于控制變量的初始化,這個(gè)變量的讀寫滿足如下三個(gè)條件:
- 當(dāng)且僅當(dāng)?shù)谝淮卧L問某個(gè)變量時(shí),進(jìn)行初始化(寫);
- 變量初始化過程中,所有讀都被阻塞,直到初始化完成;
- 變量?jī)H初始化一次,初始化完成后駐留在內(nèi)存里。
var loadOnce sync.Once var x int for i:=0;i<10;i++{ loadOnce.Do(func() { x++ }) } fmt.Println(x)
輸出
1
sync.Cond 條件變量控制
sync.Cond
基于互斥鎖/讀寫鎖,它和互斥鎖的區(qū)別是什么呢?
互斥鎖 sync.Mutex
通常用來保護(hù)臨界區(qū)和共享資源,條件變量 sync.Cond
用來協(xié)調(diào)想要訪問共享資源的 goroutine
。
也就是在存在共享變量時(shí),可以直接使用sync.Cond
來協(xié)調(diào)共享變量,比如最常見的共享隊(duì)列,多消費(fèi)多生產(chǎn)的模式。
我一開始也很疑惑為什么不使用channel
和select
的模式來做生產(chǎn)者消費(fèi)者模型(實(shí)際上也可以),這一節(jié)不是重點(diǎn)就不展開討論了。
創(chuàng)建實(shí)例
func NewCond(l Locker) *Cond
NewCond
創(chuàng)建 Cond
實(shí)例時(shí),需要關(guān)聯(lián)一個(gè)鎖。
廣播喚醒所有
func (c *Cond) Broadcast()
Broadcast
喚醒所有等待條件變量 c
的 goroutine
,無需鎖保護(hù)。
喚醒一個(gè)協(xié)程
func (c *Cond) Signal()
Signal
只喚醒任意 1 個(gè)等待條件變量 c
的 goroutine
,無需鎖保護(hù)。
等待
func (c *Cond) Wait()
每個(gè) Cond 實(shí)例都會(huì)關(guān)聯(lián)一個(gè)鎖 L(互斥鎖 *Mutex,或讀寫鎖 *RWMutex),當(dāng)修改條件或者調(diào)用 Wait 方法時(shí),必須加鎖。
舉個(gè)不恰當(dāng)?shù)睦樱瑢?shí)現(xiàn)一個(gè)經(jīng)典的生產(chǎn)者和消費(fèi)者模式,但有先決條件:
- 邊生產(chǎn)邊消費(fèi),可以多生產(chǎn)多消費(fèi)。
- 生產(chǎn)后通知消費(fèi)。
- 隊(duì)列為空時(shí),暫停等待。
- 支持關(guān)閉,關(guān)閉后等待消費(fèi)結(jié)束。
- 關(guān)閉后依然可以生產(chǎn),但無法消費(fèi)了。
var ( cnt int shuttingDown = false cond = sync.NewCond(&sync.Mutex{}) )
cnt
為隊(duì)列,這里直接用變量代替了,變量就是隊(duì)列長(zhǎng)度。shuttingDown
消費(fèi)關(guān)閉狀態(tài)。cond
現(xiàn)成的隊(duì)列控制。
生產(chǎn)者
func Add(entry int) { cond.L.Lock() defer cond.L.Unlock() cnt += entry fmt.Println("生產(chǎn)咯,來消費(fèi)吧") cond.Signal() }
消費(fèi)者
func Get() (int, bool) { cond.L.Lock() defer cond.L.Unlock() for cnt == 0 && !shuttingDown { fmt.Println("未關(guān)閉但空了,等待生產(chǎn)") cond.Wait() } if cnt == 0 { fmt.Println("關(guān)閉咯,也消費(fèi)完咯") return 0, true } cnt-- return 1, false }
關(guān)閉程序
func Shutdown() { cond.L.Lock() defer cond.L.Unlock() shuttingDown = true fmt.Println("要關(guān)閉咯,大家快消費(fèi)") cond.Broadcast() }
主程序
var wg sync.WaitGroup wg.Add(2) time.Sleep(time.Second) go func() { defer wg.Done() for i := 0; i < 10; i++ { go Add(1) if i%5 == 0 { time.Sleep(time.Second) } } }() go func() { defer wg.Done() shuttingDown := false for !shuttingDown { var cur int cur, shuttingDown = Get() fmt.Printf("當(dāng)前消費(fèi) %d, 隊(duì)列剩余 %d \n", cur, cnt) } }() time.Sleep(time.Second * 5) Shutdown() wg.Wait()
- 分別創(chuàng)建生產(chǎn)者與消費(fèi)者。
- 生產(chǎn)10個(gè),每5個(gè)休息1秒。
- 持續(xù)消費(fèi)。
- 主程序關(guān)閉隊(duì)列。
輸出
生產(chǎn)咯,來消費(fèi)吧
當(dāng)前消費(fèi) 1, 隊(duì)列剩余 0
未關(guān)閉但空了,等待生產(chǎn)
生產(chǎn)咯,來消費(fèi)吧
生產(chǎn)咯,來消費(fèi)吧
當(dāng)前消費(fèi) 1, 隊(duì)列剩余 1
當(dāng)前消費(fèi) 1, 隊(duì)列剩余 0
未關(guān)閉但空了,等待生產(chǎn)
生產(chǎn)咯,來消費(fèi)吧
生產(chǎn)咯,來消費(fèi)吧
生產(chǎn)咯,來消費(fèi)吧
當(dāng)前消費(fèi) 1, 隊(duì)列剩余 2
當(dāng)前消費(fèi) 1, 隊(duì)列剩余 1
當(dāng)前消費(fèi) 1, 隊(duì)列剩余 0
未關(guān)閉但空了,等待生產(chǎn)
生產(chǎn)咯,來消費(fèi)吧
生產(chǎn)咯,來消費(fèi)吧
生產(chǎn)咯,來消費(fèi)吧
生產(chǎn)咯,來消費(fèi)吧
當(dāng)前消費(fèi) 1, 隊(duì)列剩余 1
當(dāng)前消費(fèi) 1, 隊(duì)列剩余 2
當(dāng)前消費(fèi) 1, 隊(duì)列剩余 1
當(dāng)前消費(fèi) 1, 隊(duì)列剩余 0
未關(guān)閉但空了,等待生產(chǎn)
要關(guān)閉咯,大家快消費(fèi)
關(guān)閉咯,也消費(fèi)完咯
當(dāng)前消費(fèi) 0, 隊(duì)列剩余 0
小結(jié)
1.sync.Map 并發(fā)安全的Map。
2.sync.Once 只執(zhí)行一次,適用于配置讀取、通道關(guān)閉。
3.sync.Cond 控制協(xié)調(diào)共享資源。
以上就是GO語言并發(fā)之好用的sync包詳解的詳細(xì)內(nèi)容,更多關(guān)于GO語言 sync包的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
jenkins構(gòu)建go及java項(xiàng)目的方法
這篇文章主要介紹了jenkins構(gòu)建go及java項(xiàng)目,本文通過圖文實(shí)例相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值了,需要的朋友可以參考下2021-04-04Golang錯(cuò)誤處理:異常捕捉和恢復(fù)機(jī)制
Golang中,異常處理是通過 defer + panic + recover 的方式來實(shí)現(xiàn)的,使用 defer 可以將清理操作注冊(cè)到函數(shù)執(zhí)行完畢后執(zhí)行,而 panic 和 recover 可以用于處理異常,通過組合使用這些功能,可以實(shí)現(xiàn)更加健壯的程序2024-01-01教你利用Golang可選參數(shù)實(shí)現(xiàn)可選模式
本文討論Golang函數(shù)可選參數(shù)及函數(shù)類型,以及如何利用可選函數(shù)類型實(shí)現(xiàn)可選模式。同時(shí)通過構(gòu)造函數(shù)作為示例,實(shí)現(xiàn)強(qiáng)大帶可選參數(shù)的構(gòu)造函數(shù),讓代碼更直觀、靈活、支持?jǐn)U展2023-01-01Go語言服務(wù)器開發(fā)之客戶端向服務(wù)器發(fā)送數(shù)據(jù)并接收返回?cái)?shù)據(jù)的方法
這篇文章主要介紹了Go語言服務(wù)器開發(fā)之客戶端向服務(wù)器發(fā)送數(shù)據(jù)并接收返回?cái)?shù)據(jù)的方法,實(shí)例分析了客戶端的開發(fā)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02基于go手動(dòng)寫個(gè)轉(zhuǎn)發(fā)代理服務(wù)的代碼實(shí)現(xiàn)
這篇文章主要介紹了基于go手動(dòng)寫個(gè)轉(zhuǎn)發(fā)代理服務(wù)的代碼實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-02-02Go語言for-range函數(shù)使用技巧實(shí)例探究
這篇文章主要為大家介紹了Go語言for-range函數(shù)使用技巧實(shí)例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01一文教你如何快速學(xué)會(huì)Go的切片和數(shù)組數(shù)據(jù)類型
數(shù)組是屬于同一類型的元素的集合。切片是數(shù)組頂部的方便、靈活且功能強(qiáng)大的包裝器。本文就來和大家聊聊Go中切片和數(shù)組的使用,需要的可以參考一下2023-03-03Golang中Error的設(shè)計(jì)與實(shí)踐詳解
這篇文章主要為大家詳細(xì)介紹了Golang中Error的設(shè)計(jì)以及是具體如何處理錯(cuò)誤的相關(guān)知識(shí),文中的示例代碼簡(jiǎn)潔易懂,需要的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-08-08Go實(shí)現(xiàn)跨平臺(tái)的藍(lán)牙聊天室示例詳解
這篇文章主要為大家介紹了Go實(shí)現(xiàn)跨平臺(tái)的藍(lán)牙聊天室示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12