Golang的鎖機(jī)制與使用技巧小結(jié)
1. sync.Mutex詳解
sync.Mutex是Go中的互斥鎖,通過(guò).lock()方法上鎖,.unlock()方法解鎖。需要注意的是,因?yàn)镚o函數(shù)值傳遞的特點(diǎn),sync.Mutex通過(guò)函數(shù)傳遞時(shí),會(huì)進(jìn)行一次拷貝,所以傳遞過(guò)去的鎖是一把全新的鎖,大家在使用時(shí)要注意這一點(diǎn),另外sync.Mutex是非重入鎖,這一點(diǎn)要與Java中的鎖區(qū)分。
type Mutex {
state int32
sema uint32
}上面數(shù)據(jù)結(jié)構(gòu)中的state最低三位分別表示 mutexLocked、mutexWoken 和 mutexStarving,剩下的位置用來(lái)表示當(dāng)前有多少個(gè) Goroutine 等待互斥鎖的釋放:
32 3 2 1 0 | | | | | | | | | | v-----------------------------------------------v-------------v-------------v-------------+ | | | | v | waitersCount |mutexStarving| mutexWoken | mutexLocked | | | | | | +-----------------------------------------------+-------------+-------------+-------------+
- mutexLocked — 表示互斥鎖的鎖定狀態(tài);
- mutexWoken — 表示從正常模式被從喚醒;
- mutexStarving — 當(dāng)前的互斥鎖進(jìn)入饑餓狀態(tài);
- waitersCount — 當(dāng)前互斥鎖上等待的 goroutine 個(gè)數(shù);
2. RWMutex詳解
type RWMutex struct {
w Mutex // 復(fù)用互斥鎖
writerSem uint32 // 寫(xiě)鎖監(jiān)聽(tīng)讀鎖釋放的信號(hào)量
readerSem uint32 // 讀鎖監(jiān)聽(tīng)寫(xiě)鎖釋放的信號(hào)量
readerCount int32 // 當(dāng)前正在執(zhí)行讀操作的數(shù)量
readerWait int32 // 當(dāng)寫(xiě)操作被阻塞時(shí),需要等待讀操作完成的個(gè)數(shù)
}- 讀操作如何防止并發(fā)讀寫(xiě)問(wèn)題的?
RLock(): 申請(qǐng)讀鎖,每次執(zhí)行此函數(shù)后,會(huì)對(duì)readerCount++,此時(shí)當(dāng)有寫(xiě)操作執(zhí)行Lock()時(shí)會(huì)判斷readerCount>0,就會(huì)阻塞。
RUnLock(): 解除讀鎖,執(zhí)行readerCount–,釋放信號(hào)量喚醒等待寫(xiě)操作的goroutine。
- 寫(xiě)操作如何防止并發(fā)讀寫(xiě)、并發(fā)寫(xiě)寫(xiě)問(wèn)題?
Lock(): 申請(qǐng)寫(xiě)鎖,獲取互斥鎖,此時(shí)會(huì)阻塞其他的寫(xiě)操作。并將readerCount 置為 -1,當(dāng)有讀操作進(jìn)來(lái),發(fā)現(xiàn)readerCount = -1, 即知道有寫(xiě)操作在進(jìn)行,阻塞。
Unlock(): 解除寫(xiě)鎖,會(huì)先通知所有阻塞的讀操作goroutine,然后才會(huì)釋放持有的互斥鎖。
- 寫(xiě)操作的饑餓問(wèn)題?
這是由于寫(xiě)操作要等待讀操作結(jié)束后才可以獲得鎖,而寫(xiě)操作在等待期間可能還有新的讀操作持續(xù)到來(lái),如果寫(xiě)操作等待所有讀操作結(jié)束,很可能會(huì)一直阻塞,這種現(xiàn)象稱之為寫(xiě)操作被餓死。
通過(guò)RWMutex結(jié)構(gòu)體中的readerWait屬性可完美解決這個(gè)問(wèn)題。
當(dāng)寫(xiě)操作到來(lái)時(shí),會(huì)把RWMutex.readerCount值拷貝到RWMutex.readerWait中,用于標(biāo)記排在寫(xiě)操作前面的讀者個(gè)數(shù)。
前面的讀操作結(jié)束后,除了會(huì)遞減RWMutex.readerCount,還會(huì)遞減RWMutex.readerWait值,當(dāng)RWMutex.readerWait值變?yōu)?時(shí)喚醒寫(xiě)操作。
3. sync.Map詳解
一般情況下解決并發(fā)讀寫(xiě) map 的思路是加一把大鎖,或者把一個(gè) map 分成若干個(gè)小 map,對(duì) key 進(jìn)行哈希,只操作相應(yīng)的小 map。前者鎖的粒度比較大,影響效率;后者實(shí)現(xiàn)起來(lái)比較復(fù)雜,容易出錯(cuò)。
而使用 sync.map 之后,對(duì) map 的讀寫(xiě),不需要加鎖。并且它通過(guò)空間換時(shí)間的方式,使用 read 和 dirty 兩個(gè) map 來(lái)進(jìn)行讀寫(xiě)分離,降低鎖時(shí)間來(lái)提高效率。
type Map struct {
mu Mutex
read atomic.Value // readOnly
dirty map[interface{}]*entry
misses int
}
// readOnly is an immutable struct stored atomically in the Map.read field.
type readOnly struct {
m map[interface{}]*entry
amended bool // true if the dirty map contains some key not in m.
}
type entry struct {
p unsafe.Pointer // *interface{}
}
在進(jìn)行讀操作的時(shí)候,會(huì)先在read中找,沒(méi)有命中的話會(huì)鎖住dirty并且尋找,如果找到了miss計(jì)數(shù)+1,超過(guò)閾值時(shí)將dirty賦值給read;
在進(jìn)行添加操作時(shí),直接在dirty中添加;
在進(jìn)行修改操作時(shí),先改read,再改dirty;
在進(jìn)行刪除操作時(shí),將read中加上amended標(biāo)記,dirty中直接刪除。
4. 原子操作 atomic.Value
愿此操作的底層是靠 MESI 緩存一致性協(xié)議來(lái)維持的。
Go的 atomic.Value 需要注意應(yīng)該放入只讀對(duì)象。
//atomic.Value源碼
type Value struct {
v interface{} // 所以可以存儲(chǔ)任何類型的數(shù)據(jù)
}
// 空 interface{} 的內(nèi)部表示格式,作用是將interface{}類型分解,得到其中兩個(gè)字段
type ifaceWords struct {
typ unsafe.Pointer
data unsafe.Pointer
}
// 取數(shù)據(jù)就是正常走流程
func (v *Value) Load() (x interface{}) {
vp := (*ifaceWords)(unsafe.Pointer(v))
typ := LoadPointer(&vp.typ)
if typ == nil || uintptr(typ) == ^uintptr(0) {
// 第一次還沒(méi)寫(xiě)入
return nil
}
// 構(gòu)造新的interface{}返回出去
data := LoadPointer(&vp.data)
xp := (*ifaceWords)(unsafe.Pointer(&x))
xp.typ = typ
xp.data = data
return
}
// 寫(xiě)數(shù)據(jù)(如何保證數(shù)據(jù)完整性)
func (v *Value) Store(x interface{}) {
if x == nil {
panic("sync/atomic: store of nil value into Value")
}
// 繞過(guò) Go 語(yǔ)言類型系統(tǒng)的檢查,與任意的指針類型互相轉(zhuǎn)換
vp := (*ifaceWords)(unsafe.Pointer(v)) // 舊值
xp := (*ifaceWords)(unsafe.Pointer(&x)) // 新值
for { // 配合CompareAndSwap達(dá)到樂(lè)觀鎖的功效
typ := LoadPointer(&vp.typ)
if typ == nil { // 第一次寫(xiě)入
runtime_procPin() // 禁止搶占
if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(^uintptr(0))) {
runtime_procUnpin() // 沒(méi)有搶到鎖,說(shuō)明已經(jīng)有別的線程搶先完成賦值,重新進(jìn)入循環(huán)
continue
}
// 首次賦值
StorePointer(&vp.data, xp.data)
StorePointer(&vp.typ, xp.typ)
runtime_procUnpin() // 寫(xiě)入成功,解除占用狀態(tài)
return
}
if uintptr(typ) == ^uintptr(0) {
// 第一次寫(xiě)入還未完成,繼續(xù)等待
continue
}
// 兩次需要寫(xiě)入相同類型
if typ != xp.typ {
panic("sync/atomic: store of inconsistently typed value into Value")
}
StorePointer(&vp.data, xp.data)
return
}
}
// 禁止搶占,標(biāo)記當(dāng)前G在M上不會(huì)被搶占,并返回當(dāng)前所在P的ID。
func runtime_procPin()
// 解除G的禁止搶占狀態(tài),之后G可被搶占。
func runtime_procUnpin()5. 使用小技巧
- 減小臨界區(qū)域(減少鎖的持有時(shí)間)
var m sync.Mutex
func DoSth() {
// do sth1
func() {
u.lock()
defer m.unlock()
// do sth2
}()
// do sth3
}如上所示,如果do sth3中是很費(fèi)時(shí)的io操作,使用這個(gè)技巧可以將臨界區(qū)減小,提高性能,不過(guò),如果本身臨界區(qū)就不大,鎖操作后續(xù)沒(méi)有什么費(fèi)時(shí)操作,那么也就沒(méi)有必要這樣操作了。
- 減小鎖的粒度
在高并發(fā)場(chǎng)景下,用鎖的數(shù)量來(lái)?yè)Q取并發(fā)效率,類似于java中ConcurrentHashmap的分段鎖思想,增加鎖的數(shù)量,減少一把鎖控制的數(shù)據(jù)量。
- 讀寫(xiě)分離(讀寫(xiě)鎖): RWMutex,sync.Map
在讀多寫(xiě)少的情景下,可以使用讀寫(xiě)鎖,提高讀操作的并發(fā)性能。
- 使用原子操作
原子操作是CPU指令級(jí)的操作,不會(huì)觸發(fā)g調(diào)度機(jī)制。,不阻塞執(zhí)行流
到此這篇關(guān)于Golang的鎖機(jī)制與使用技巧精選的文章就介紹到這了,更多相關(guān)Golang 鎖機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang使用sqlite3數(shù)據(jù)庫(kù)實(shí)現(xiàn)CURD操作
這篇文章主要為大家詳細(xì)介紹了Golang使用sqlite3數(shù)據(jù)庫(kù)實(shí)現(xiàn)CURD操作的相關(guān)知識(shí),文中的示例代碼簡(jiǎn)潔易懂,有需要的小伙伴可以參考一下2025-03-03
Golang實(shí)現(xiàn)Md5校驗(yàn)的代碼示例
最近項(xiàng)目中有個(gè)需求,就是地圖文件下發(fā)后,接收方需要文件的md5值,和接收到的文件做比對(duì),以免文件不完整,引起bug,于是測(cè)試了下本地文件和遠(yuǎn)程文件的md5計(jì)算,所以本文給大家介紹了Golang實(shí)現(xiàn)Md5校驗(yàn),需要的朋友可以參考下2024-07-07
Golang使用WebSocket通信的實(shí)現(xiàn)
這篇文章主要介紹了Golang使用WebSocket通信的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02
Go語(yǔ)言中g(shù)oroutine和WaitGroup的使用示例詳解
goroutine 是Go中一個(gè)輕量級(jí)的線程, 只需要一個(gè)go關(guān)鍵字就可以創(chuàng)建一個(gè)goroutine,這篇文章主要介紹了Go語(yǔ)言中g(shù)oroutine和WaitGroup的使用,需要的朋友可以參考下2023-03-03
Go語(yǔ)言開(kāi)發(fā)redis封裝及簡(jiǎn)單使用詳解
這篇文章主要為大家介紹了Go語(yǔ)言開(kāi)發(fā)redis的封裝及簡(jiǎn)單使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2021-11-11
GO語(yǔ)言不固定參數(shù)函數(shù)與匿名函數(shù)的使用
本文主要介紹了GO語(yǔ)言不固定參數(shù)函數(shù)與匿名函數(shù)的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03

