網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
前言
在學(xué)習(xí)操作系統(tǒng)的時(shí)候,我們應(yīng)該都學(xué)習(xí)過(guò)臨界區(qū)、互斥鎖這些概念,用于在并發(fā)環(huán)境下保證狀態(tài)的正確性。比如在秒殺時(shí),100 個(gè)用戶同時(shí)搶 10 個(gè)電腦,為了避免少賣或者超賣,就需要使用鎖來(lái)進(jìn)行并發(fā)控制。在 Go語(yǔ)言 里面互斥鎖是?sync.Mutex
?,我們本篇文章就來(lái)學(xué)習(xí)下為什么要使用互斥鎖、如何使用互斥鎖,以及使用時(shí)的常見問(wèn)題。
為什么要使用互斥鎖
我們來(lái)看一個(gè)示例:我們起了?10000
?個(gè)協(xié)程將變量?num
?加1,因此肯定會(huì)存在并發(fā),如果我們不控制并發(fā),10000 個(gè)協(xié)程都執(zhí)行完后,該變量的值很大概率不等于 10000。
那么為什么會(huì)出現(xiàn)這個(gè)問(wèn)題呢,原因是?num++
?不是原子操作,它會(huì)先讀取變量?num
?當(dāng)前值,然后對(duì)這個(gè)值?加1
,再把結(jié)果保存到?num
?中。例如?10
?個(gè)?goroutine
?同時(shí)運(yùn)行到?num++
?這一行,可能同時(shí)讀取?num=1000
,都加1
后再保存,?num=1001
,這就與想要的結(jié)果不符。
package?main import?( ?"fmt" ?"sync" ) func?main()?{ ?num?:=?0 ?var?wg?sync.WaitGroup ?threadCount?:=?10000 ?wg.Add(threadCount) ?? ?for?i?:=?0;?i?<?threadCount;?i++?{ ??go?func()?{ ???defer?wg.Done() ???num++ ??}() ?} ?? ?wg.Wait()?//?等待?10000?個(gè)協(xié)程都執(zhí)行完 ??fmt.Println(num)?//?9388(每次都可能不一樣) }
我們?nèi)绻褂昧嘶コ怄i,可以保證每次進(jìn)入臨界區(qū)的只有一個(gè)?goroutine
,一個(gè)?goroutine
?執(zhí)行完后,另一個(gè)?goroutine
?才能進(jìn)入臨界區(qū)執(zhí)行,最終就實(shí)現(xiàn)了并發(fā)控制。
并發(fā)獲取鎖示意圖
package?main import?( ?"fmt" ?"sync" ) func?main()?{ ?num?:=?0 ?var?mutex?sync.Mutex??//?互斥鎖 ?var?wg?sync.WaitGroup ?threadCount?:=?10000 ?wg.Add(threadCount) ?for?i?:=?0;?i?<?threadCount;?i++?{ ??go?func()?{ ???defer?wg.Done() ??? ???mutex.Lock()?//?加鎖 ???num++?//?臨界區(qū) ???mutex.Unlock()?//?解鎖 ??? ??}() ?} ?wg.Wait() ?fmt.Println(num)?//?10000 }
如何使用互斥鎖
Mutex
?保持?Go
?一貫的簡(jiǎn)潔風(fēng)格,開箱即用,聲明一個(gè)變量默認(rèn)是沒(méi)有加鎖的,加鎖使用?Lock()
?方法,解鎖使用?Unlock()
?方法。
使用方式一:直接聲明使用
這個(gè)在上例中已經(jīng)體現(xiàn)了,直接看上面的例子就好
使用方式二:封裝在其他結(jié)構(gòu)體中
我們可以將?Mutex
?封裝在?struct
?中,封裝成線程安全的函數(shù)供外部調(diào)用。比如我們封裝了一個(gè)線程安全的計(jì)數(shù)器,調(diào)用?Add()
?就加一,調(diào)用Count()
?返回計(jì)數(shù)器的值。
package?main import?( ?"fmt" ?"sync" ) type?Counter?struct?{ ?num???int ?mutex?sync.Mutex } //?加一操作,涉及到臨界區(qū)?num,加鎖解鎖 func?(counter?*Counter)?Add()?{ ?counter.mutex.Lock() ?defer?counter.mutex.Unlock() ?counter.num++ } //?返回?cái)?shù)量,涉及到臨界區(qū)?num,加鎖解鎖 func?(counter?*Counter)?Count()?int?{ ?counter.mutex.Lock() ?defer?counter.mutex.Unlock() ?return?counter.num } func?main()?{ ?threadCount?:=?10000 ?? ?var?counter?Counter ?var?wg?sync.WaitGroup ? ?wg.Add(threadCount) ?for?i?:=?0;?i?<?threadCount;?i++?{ ??go?func()?{ ???defer?wg.Done() ???counter.Add() ??}() ?} ?wg.Wait()?//?等待所有?goroutine?都執(zhí)行完 ?fmt.Println(counter.Count())?//?10000 }
在?Go
?中,map
?結(jié)構(gòu)是不支持并發(fā)的,如果并發(fā)讀寫就會(huì)?panic
//?運(yùn)行會(huì) panic,提示 fatal error: concurrent map writes func?main()?{ ?m?:=?make(map[string]string) ?var?wait?sync.WaitGroup ?wait.Add(1000) ?for?i?:=?0;?i?<?1000;?i++?{ ??item?:=?fmt.Sprintf("%d",?i) ??go?func()?{ ???wait.Done() ???m[item]?=?item ??}() ?} ?wait.Wait() }
基于?Mutex
?,我們可以實(shí)現(xiàn)一個(gè)線程安全的?map
:
import?( ?"fmt" ?"sync" ) type?ConcurrentMap?struct?{ ?mutex?sync.Mutex ?items?map[string]interface{} } func?(c?*ConcurrentMap)?Add(key?string,?value?interface{})?{ ?c.mutex.Lock() ?defer?c.mutex.Unlock() ?c.items[key]?=?value } func?(c?*ConcurrentMap)?Remove(key?string)?{ ?c.mutex.Lock() ?defer?c.mutex.Unlock() ?delete(c.items,?key) } func?(c?*ConcurrentMap)?Get(key?string)?interface{}?{ ?c.mutex.Lock() ?defer?c.mutex.Unlock() ?return?c.items[key] } func?NewConcurrentMap()?ConcurrentMap?{ ?return?ConcurrentMap{ ??items:?make(map[string]interface{}), ?} } func?main()?{ ?m?:=?NewConcurrentMap() ?var?wait?sync.WaitGroup ?wait.Add(1000) ?for?i?:=?0;?i?<?1000;?i++?{ ??item?:=?fmt.Sprintf("%d",?i) ??go?func()?{ ???wait.Done() ???m.Add(item,?item) ??}() ?} ?wait.Wait() ?fmt.Println(m.Get("100"))?//?100 }
當(dāng)然,基于互斥鎖?Mutex
?實(shí)現(xiàn)的線程安全?map
?并不是性能最好的,基于讀寫鎖?sync.RWMutex
?和 分片 可以實(shí)現(xiàn)性能更好的、線程安全的?map
,開發(fā)中比較常用的并發(fā)安全?map
?是 orcaman / concurrent-map(https://github.com/orcaman/concurrent-map)。
互斥鎖的常見問(wèn)題
從上面可以看出,Mutex
?的使用過(guò)程方法比較簡(jiǎn)單,但還是有幾點(diǎn)需要注意:
1.Mutex
是可以在?goroutine A
?中加鎖,在?goroutine B
?中解鎖的,但是在實(shí)際使用中,盡量保證在同一個(gè) goroutine 中加解鎖。比如 goroutine A 申請(qǐng)到了鎖,在處理臨界區(qū)資源的時(shí)候,goroutine B 把鎖釋放了,但是 A 以為自己還持有鎖,會(huì)繼續(xù)處理臨界區(qū)資源,就可能會(huì)出現(xiàn)問(wèn)題。
2.Mutex
的加鎖解鎖基本都是成對(duì)出現(xiàn),為了解決忘記解鎖,可以使用?defer
?語(yǔ)句,在加鎖后直接?defer mutex.Unlock()
;但是如果處理完臨界區(qū)資源后還有很多耗時(shí)操作,為了盡早釋放鎖,不建議使用?defer
,而是在處理完臨界區(qū)資源后就調(diào)用?mutex.Unlock()
?盡早釋放鎖。
//?邏輯復(fù)雜,可能會(huì)忘記釋放鎖 func?main()?{ ?var?mutex?sync.Mutex ?mutex.Lock() ?if?***?{ ??if?***?{ ???//?處理臨界區(qū)資源 ???mutex.Unlock() ???return ??} ??//?處理臨界區(qū)資源 ??mutex.Unlock() ??return ?} ?//?處理臨界區(qū)資源 ?mutex.Unlock() ?return } //?避免邏輯復(fù)雜忘記釋放鎖,使用?defer語(yǔ)句,成對(duì)出現(xiàn) func?main()?{ ?var?mutex?sync.Mutex ?mutex.Lock() ?defer?mutex.Unlock() ?if?***?{ ??if?***?{ ???//?處理臨界區(qū)資源 ???return ??} ??//?處理臨界區(qū)資源 ??return ?} ?//?處理臨界區(qū)資源 ?return }
3.Mutex 不能復(fù)制使用
Mutex
?是有狀態(tài)的,比如我們對(duì)一個(gè)?Mutex
?加鎖后,再進(jìn)行復(fù)制操作,會(huì)把當(dāng)前的加鎖狀態(tài)也給復(fù)制過(guò)去,基于加鎖的?Mutex
?再加鎖肯定不會(huì)成功。進(jìn)行復(fù)制操作可能聽起來(lái)是一個(gè)比較低級(jí)的錯(cuò)誤,但是無(wú)意間可能就會(huì)犯這種錯(cuò)誤。
package?main import?( ?"fmt" ?"sync" ) type?Counter?struct?{ ?mutex?sync.Mutex ?num???int } func?SomeFunc(c?Counter)?{ ?c.mutex.Lock() ?defer?c.mutex.Unlock() ?c.num-- } func?main()?{ ?var?counter?Counter ?counter.mutex.Lock() ?defer?counter.mutex.Unlock() ?counter.num++ ?//?Go都是值傳遞,這里復(fù)制了?counter,此時(shí)?counter.mutex?是加鎖狀態(tài),在?SomeFunc?無(wú)法再次加鎖,就會(huì)一直等待 ?SomeFunc(counter) }
原文鏈接:https://mp.weixin.qq.com/s/2wKsjLcngrcaiHGe2egttw
相關(guān)推薦
- 2023-11-14 Kubernetes常用命令(持續(xù)更新)
- 2022-07-12 linux中刪除指定文件以外的其它所有文件
- 2022-05-10 bean屬性xml方式的自動(dòng)裝配
- 2022-12-04 Python中Yield的基本用法及Yield與return的區(qū)別解析_python
- 2022-07-07 python計(jì)算階乘的兩個(gè)函數(shù)用法_python
- 2022-06-22 C語(yǔ)言詳解如何實(shí)現(xiàn)堆及堆的結(jié)構(gòu)與接口_C 語(yǔ)言
- 2022-05-15 React中的Diff算法你了解嗎_React
- 2024-03-15 Redis中RDB和AOF
- 最近更新
-
- 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)證過(guò)濾器
- 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)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支