Go 為什么不支持可重入鎖?
本文轉(zhuǎn)載自微信公眾號(hào)「腦子進(jìn)煎魚(yú)了」,作者陳煎魚(yú)。轉(zhuǎn)載本文請(qǐng)聯(lián)系腦子進(jìn)煎魚(yú)了公眾號(hào)。
大家好,我是煎魚(yú)。
程序里的鎖,是很多小伙伴在寫分布式應(yīng)用時(shí)用的最多的一個(gè)利器之一。
使用 Go 的同學(xué)里,絕大部分都有其他語(yǔ)言的經(jīng)驗(yàn),就會(huì)對(duì)其中一點(diǎn)有疑惑,那就是 Go 里的鎖,竟然不支持可重入?
為此,今天煎魚(yú)帶大家一起來(lái)了解這里的設(shè)計(jì)考量,看看為什么。
可重入鎖
如果對(duì)已經(jīng)上鎖的普通互斥鎖進(jìn)行 “加鎖” 操作,其結(jié)果要么失敗,要么會(huì)阻塞至解鎖。
鎖的場(chǎng)景如下:
- 在加鎖上:如果是可重入互斥鎖,當(dāng)前嘗試加鎖的線程如果就是持有該鎖的線程時(shí),加鎖操作就會(huì)成功。
- 在解鎖上:可重入互斥鎖一般都會(huì)記錄被加鎖的次數(shù),只有執(zhí)行相同次數(shù)的解鎖操作才會(huì)真正解鎖。
簡(jiǎn)單來(lái)講,可重入互斥鎖是互斥鎖的一種,同一線程對(duì)其多次加鎖不會(huì)產(chǎn)生死鎖,又或是導(dǎo)致阻塞。
不同語(yǔ)言間實(shí)現(xiàn)可能或多或少有些區(qū)別,但大體意思差不多。
請(qǐng)你想一下,Go 是怎么樣的呢?
Go 支持情況
我們看到以下這個(gè) Go 互斥鎖例子:
- var mu sync.Mutex
- func main() {
- mu.Lock()
- mu.Lock()
- }
這段 Go 程序會(huì)阻塞嗎?不會(huì),會(huì)報(bào)以下錯(cuò)誤:
- fatal error: all goroutines are asleep - deadlock!
Go 顯然是不支持可重入互斥鎖的。
官方回復(fù)
Go 設(shè)計(jì)原則
在工程中使用互斥的根本原因是:為了保護(hù)不變量,也可以用于保護(hù)內(nèi)、外部的不變量。
基于此,Go 在互斥鎖設(shè)計(jì)上會(huì)遵守這幾個(gè)原則。如下:
- 在調(diào)用 mutex.Lock 方法時(shí),要保證這些變量的不變性保持,不會(huì)在后續(xù)的過(guò)程中被破壞。
- 在調(diào)用 mu.Unlock 方法時(shí),要保證:
- 程序不再需要依賴那些不變量。
- 如果程序在互斥鎖加鎖期間破壞了它們,則需要確保已經(jīng)恢復(fù)了它們。
不支持的原因
講了 Go 自己的設(shè)計(jì)原則后,那為什么不支持可重入呢?
其實(shí) Russ Cox 于 2010 年在《Experimenting with GO[1]》就給出了答復(fù),認(rèn)為遞歸(又稱:重入)互斥是個(gè)壞主意,這個(gè)設(shè)計(jì)并不好。
我們可以結(jié)合官方的例子來(lái)理解。
如下:
- func F() {
- mu.Lock()
- ... do some stuff ...
- G()
- ... do some more stuff ...
- mu.Unlock()
- }
- func G() {
- mu.Lock()
- ... do some stuff ...
- mu.Unlock()
- }
在上述代碼中,我們?cè)?F 方法中調(diào)用 mu.Lock 方法加上了鎖。如果支持可重入鎖,接著就會(huì)進(jìn)入到 G 方法中。
此時(shí)就會(huì)有一個(gè)致命的問(wèn)題,你不知道 F 和 G 方法加鎖后是不是做了什么事情,從而導(dǎo)致破壞了不變量,畢竟隨手起幾個(gè)協(xié)程做點(diǎn)壞事,也是完全可能的。
這對(duì)于 Go 是無(wú)法接受的,可重入的設(shè)計(jì)違反了前面所提到的設(shè)計(jì)理念,也就是:“要保證這些變量的不變性保持,不會(huì)在后續(xù)的過(guò)程中被破壞”。
基于上述原因,Go 官方團(tuán)隊(duì)選擇了沒(méi)有支持該項(xiàng)特性。
總結(jié)
Go 互斥鎖沒(méi)有支持可重入鎖的設(shè)計(jì),也是喜歡的大道至簡(jiǎn)的思路了,可能的干擾比較多,不如直接簡(jiǎn)單的來(lái)。
你在工作過(guò)程中有沒(méi)有類似的疑惑呢,歡迎大家在評(píng)論區(qū)留言和交流:)
參考資料
[1]Experimenting with GO: https://groups.google.com/g/golang-nuts/c/XqW1qcuZgKg/m/Ui3nQkeLV80J