網(wǎng)站首頁 編程語言 正文
sync.Map 并發(fā)安全的Map
反例如下,兩個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í)行報錯:
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
等操作方法,自行體驗。
sync.Once 只執(zhí)行一次
很多場景下我們需要確保某些操作在高并發(fā)的場景下只執(zhí)行一次,例如只加載一次配置文件、只關(guān)閉一次通道等。
init
函數(shù)是當(dāng)所在的 package
首次被加載時執(zhí)行,若遲遲未被使用,則既浪費了內(nèi)存,又延長了程序加載時間。
sync.Once
可以在代碼的任意位置初始化和調(diào)用,因此可以延遲到使用時再執(zhí)行,并發(fā)場景下是線程安全的。
在多數(shù)情況下,sync.Once
被用于控制變量的初始化,這個變量的讀寫滿足如下三個條件:
- 當(dāng)且僅當(dāng)?shù)谝淮卧L問某個變量時,進行初始化(寫);
- 變量初始化過程中,所有讀都被阻塞,直到初始化完成;
- 變量僅初始化一次,初始化完成后駐留在內(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
通常用來保護臨界區(qū)和共享資源,條件變量 sync.Cond
用來協(xié)調(diào)想要訪問共享資源的 goroutine
。
也就是在存在共享變量時,可以直接使用sync.Cond
來協(xié)調(diào)共享變量,比如最常見的共享隊列,多消費多生產(chǎn)的模式。
我一開始也很疑惑為什么不使用channel
和select
的模式來做生產(chǎn)者消費者模型(實際上也可以),這一節(jié)不是重點就不展開討論了。
創(chuàng)建實例
func NewCond(l Locker) *Cond
NewCond
創(chuàng)建 Cond
實例時,需要關(guān)聯(lián)一個鎖。
廣播喚醒所有
func (c *Cond) Broadcast()
Broadcast
喚醒所有等待條件變量 c
的 goroutine
,無需鎖保護。
喚醒一個協(xié)程
func (c *Cond) Signal()
Signal
只喚醒任意 1 個等待條件變量 c
的 goroutine
,無需鎖保護。
等待
func (c *Cond) Wait()
每個 Cond 實例都會關(guān)聯(lián)一個鎖 L(互斥鎖 *Mutex,或讀寫鎖 *RWMutex),當(dāng)修改條件或者調(diào)用 Wait 方法時,必須加鎖。
舉個不恰當(dāng)?shù)睦樱瑢崿F(xiàn)一個經(jīng)典的生產(chǎn)者和消費者模式,但有先決條件:
- 邊生產(chǎn)邊消費,可以多生產(chǎn)多消費。
- 生產(chǎn)后通知消費。
- 隊列為空時,暫停等待。
- 支持關(guān)閉,關(guān)閉后等待消費結(jié)束。
- 關(guān)閉后依然可以生產(chǎn),但無法消費了。
var ( cnt int shuttingDown = false cond = sync.NewCond(&sync.Mutex{}) )
-
cnt
為隊列,這里直接用變量代替了,變量就是隊列長度。 -
shuttingDown
消費關(guān)閉狀態(tài)。 -
cond
現(xiàn)成的隊列控制。
生產(chǎn)者
func Add(entry int) { cond.L.Lock() defer cond.L.Unlock() cnt += entry fmt.Println("生產(chǎn)咯,來消費吧") cond.Signal() }
消費者
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)閉咯,也消費完咯") return 0, true } cnt-- return 1, false }
關(guān)閉程序
func Shutdown() { cond.L.Lock() defer cond.L.Unlock() shuttingDown = true fmt.Println("要關(guān)閉咯,大家快消費") 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)前消費 %d, 隊列剩余 %d \n", cur, cnt) } }() time.Sleep(time.Second * 5) Shutdown() wg.Wait()
- 分別創(chuàng)建生產(chǎn)者與消費者。
- 生產(chǎn)10個,每5個休息1秒。
- 持續(xù)消費。
- 主程序關(guān)閉隊列。
輸出
生產(chǎn)咯,來消費吧
當(dāng)前消費 1, 隊列剩余 0?
未關(guān)閉但空了,等待生產(chǎn)
生產(chǎn)咯,來消費吧
生產(chǎn)咯,來消費吧
當(dāng)前消費 1, 隊列剩余 1?
當(dāng)前消費 1, 隊列剩余 0?
未關(guān)閉但空了,等待生產(chǎn)
生產(chǎn)咯,來消費吧
生產(chǎn)咯,來消費吧
生產(chǎn)咯,來消費吧
當(dāng)前消費 1, 隊列剩余 2?
當(dāng)前消費 1, 隊列剩余 1?
當(dāng)前消費 1, 隊列剩余 0?
未關(guān)閉但空了,等待生產(chǎn)
生產(chǎn)咯,來消費吧
生產(chǎn)咯,來消費吧
生產(chǎn)咯,來消費吧
生產(chǎn)咯,來消費吧
當(dāng)前消費 1, 隊列剩余 1?
當(dāng)前消費 1, 隊列剩余 2?
當(dāng)前消費 1, 隊列剩余 1?
當(dāng)前消費 1, 隊列剩余 0?
未關(guān)閉但空了,等待生產(chǎn)
要關(guān)閉咯,大家快消費
關(guān)閉咯,也消費完咯
當(dāng)前消費 0, 隊列剩余 0
小結(jié)
1.sync.Map 并發(fā)安全的Map。
2.sync.Once 只執(zhí)行一次,適用于配置讀取、通道關(guān)閉。
3.sync.Cond 控制協(xié)調(diào)共享資源。
原文鏈接:https://juejin.cn/post/7182059760567451685
相關(guān)推薦
- 2022-07-03 python?for循環(huán)如何實現(xiàn)控制步長_python
- 2022-12-08 linux服務(wù)器中搭建redis6.0.7集群_Redis
- 2022-09-26 使用JDBC連接數(shù)據(jù)庫執(zhí)行sql語句,創(chuàng)建數(shù)據(jù)庫連接池
- 2022-09-01 C語言全面梳理結(jié)構(gòu)體知識點_C 語言
- 2023-02-09 Flask如何接收前端ajax傳來的表單(包含文件)_python
- 2022-10-17 Kotlin編程條件控制示例詳解_Android
- 2022-01-25 項目啟動的時候報Exception in thread main 錯誤解決方法
- 2022-10-19 python用opencv將標(biāo)注提取畫框到對應(yīng)的圖像中_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支