網(wǎng)站首頁 編程語言 正文
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í)行一次
很多場景下我們需要確保某些操作在高并發(fā)的場景下只執(zhí)行一次,例如只加載一次配置文件、只關(guān)閉一次通道等。
init
函數(shù)是當(dāng)所在的 package
首次被加載時(shí)執(zhí)行,若遲遲未被使用,則既浪費(fèi)了內(nèi)存,又延長了程序加載時(shí)間。
sync.Once
可以在代碼的任意位置初始化和調(diào)用,因此可以延遲到使用時(shí)再執(zhí)行,并發(fā)場景下是線程安全的。
在多數(shù)情況下,sync.Once
被用于控制變量的初始化,這個(gè)變量的讀寫滿足如下三個(gè)條件:
- 當(dāng)且僅當(dāng)?shù)谝淮卧L問某個(gè)變量時(shí),進(jìn)行初始化(寫);
- 變量初始化過程中,所有讀都被阻塞,直到初始化完成;
- 變量僅初始化一次,初始化完成后駐留在內(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ì)列長度。 -
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)共享資源。
原文鏈接:https://juejin.cn/post/7182059760567451685
相關(guān)推薦
- 2021-12-29 Android中String與int相互轉(zhuǎn)換_Android
- 2022-06-10 SQL?Server中函數(shù)、存儲(chǔ)過程與觸發(fā)器的用法_MsSql
- 2022-02-09 C++解決輸出鏈表中倒數(shù)k個(gè)結(jié)點(diǎn)的問題_C 語言
- 2022-07-31 ubuntu下常用apt命令介紹_linux shell
- 2022-04-04 webpack-loaders: postcss
- 2022-09-25 TCP協(xié)議和UDP協(xié)議
- 2022-11-24 Python?Django中間件詳細(xì)介紹_python
- 2022-10-04 python?Pillow圖像降噪處理顏色處理_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 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錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支