Golang 的鎖機制

近日看了一篇 文章留拾,講到了用鎖的 panic 問題涕侈,但并沒有看懂羹奉,經(jīng)過多次測試秒旋,整理如下。


Golang 中的鎖

Golang 中的有兩種鎖诀拭,為 sync.Mutexsync.RWMutex迁筛。

  • sync.Mutex 的鎖只有一種鎖:Lock(),它是絕對鎖耕挨,同一時間只能有一個鎖细卧。
  • sync.RWMutex 叫讀寫鎖,它有兩種鎖: RLock()Lock()
    • RLock() 叫讀鎖筒占。它不是絕對鎖贪庙,可以有多個讀者同時獲取此鎖(調(diào)用 mu.RLock)。
    • Lock() 叫寫鎖翰苫,它是個絕對鎖止邮,就是說,如果一旦某人拿到了這個鎖奏窑,別人就不能再獲取此鎖了导披。

另外,有一種特性:

  • 當寫鎖阻塞時埃唯,新的讀鎖是無法申請的撩匕。

即在 sync.RWMutex 的使用中,一個線程請求了他的寫鎖(mx.Lock())后墨叛,即便它還沒有取到該鎖(可能由于資源已被其他人鎖定)止毕,后面所有的讀鎖的申請模蜡,都將被阻塞,只有取寫鎖的請求得到了鎖且用完釋放后滓技,讀鎖才能去取哩牍。

這種特性可以有效防止寫者 饑餓。如果一個線程因為某種原因令漂,導致得不到CPU運行時間膝昆,這種狀態(tài)被稱之為 饑餓。

另外叠必,由上面的基礎(chǔ)又衍生出一些想法并測試了一下荚孵,結(jié)果如下:

  • 讀寫鎖中的可讀鎖(sync.RWMutexRLock())可以嵌套使用的,在一個線程中單獨來看纬朝,它不會有問題(但這是踩坑點)收叶。
  • 互斥鎖(sync.Mutexsync.RWMutexLock())是不可以互相嵌套的,這是明顯的死鎖共苛。
  • sync.RWMutexLock() 不可以使用與其 RLock() 也不可以互相嵌套判没,這也是明顯的死鎖。

本篇文章的所有 嵌套 一詞均指同一個資源的鎖的嵌套隅茎。即澄峰,指一個 goroutine 在對某個資源上鎖(調(diào)用 (R)Lock())后解鎖 (調(diào)用 (R)Unlock()) 前,再次上鎖(調(diào)用 (R)Lock())辟犀。(l.RLock() -> l.RLock() -> l.RUnlock() -> l.RUnlock()

死鎖 發(fā)生時俏竞,系統(tǒng)就會報一個運行時錯誤 fatal error: all goroutines are asleep - deadlock!

可以這樣通俗地解釋這個錯誤發(fā)生的原因:一個 goroutine 請求的資源被他人鎖住堂竟,就等待它被釋放魂毁,但檢測到程序中沒有其他 goroutine 在執(zhí)行了,或者其他 goroutine 也都在等待這個鎖被某人釋放出嘹,這樣它就知道了自己永遠不會拿到這個鎖了席楚,便拋出了此死鎖的錯誤。

如下文的例子中在 10s passed 輸出后税稼,其才會報出死鎖的錯誤酣胀。

踩坑點

有些死鎖是很容易發(fā)現(xiàn)的,比如在 Lock() 自身的互相嵌套及 Lock()RLock() 的互相嵌套娶聘。

但有一種情況的死鎖不容易發(fā)現(xiàn):在嵌套使用 RLock() 時闻镶,它本身一個協(xié)程不會報錯,但當其他 goroutine 在使用 Lock() 時丸升,則有可能發(fā)生死鎖铆农。

所以為避免踩到這種坑,最好的建議就是 不要嵌套地使用 RLock()

實例與解釋

package main

import (
    "fmt"
    "sync"
    "time"
)

var l sync.RWMutex

func main() {
    go readAndRead()

    time.Sleep(1 * time.Second)
    l.Lock()
    fmt.Println("----------------- got lock")
    l.Unlock()

    time.Sleep(5 * time.Second)
}

func readAndRead() {
    l.RLock()
    fmt.Println("----------------- got rlock")
    time.Sleep(10 * time.Second)
    fmt.Println("----------------- 10s passed")

    l.RLock()
    fmt.Println("----------------- got 2nd rlock")

    l.RUnlock()
    l.RUnlock()
}

/* shell 執(zhí)行 `go run main.go` 的結(jié)果為:
   ----------------- got rlock
   ----------------- 10s passed
   fatal error: all goroutines are asleep - deadlock!
   ...
*/

上面的實例就會發(fā)生死鎖,詳細解釋其死鎖的過程如下:

  • A(goroutine readAndRead())先獲取了讀鎖
  • B (主程序)申請寫鎖的獲取墩剖,此時由于 A 加了讀鎖猴凹,因此寫鎖阻塞(等待 A 釋放讀鎖)
  • 此時 A 中又申請了讀鎖(嵌套,A 還沒有釋放前一次獲取的讀鎖)
  • 這時岭皂,由于 A 對讀鎖的申請一定會等待 B 獲取到鎖并釋放后才能得到郊霎,所以 A 和 B 都在等待鎖(A 第一次獲取到了但沒釋放,第二次的獲取卻排在了 B 的后面)爷绘。造成死鎖發(fā)生书劝。

代碼的解釋,這些條件保證了我上面的過程穩(wěn)定重現(xiàn)(可以試著打破這個過程看還會不會出錯):

  • readAndRead() 函數(shù)是在 goroutine 中執(zhí)行的土至,它會嵌套地獲取讀鎖购对。(A)
  • 主程序中會獲取寫鎖。(B)
  • 為了保證 A 先獲取到讀鎖陶因,用了 time.Sleep(1 * time.Second)骡苞,來切換時間片(或用 runtime.Gosched()),從而保證 A 和 B 兩個同時停在獲取鎖的狀態(tài)上楷扬。

總結(jié)

再次總結(jié)一下解幽,

  • 正常情況下,在請求 Lock() 鎖時發(fā)現(xiàn)資源被鎖住了烘苹,無論是 RLock() 鎖還是 Lock() 鎖亚铁,它都會等待。
  • 正常情況下螟加,在請求 RLock() 鎖時發(fā)現(xiàn)資源被 Lock() 鎖住了,它會等待吞琐。發(fā)現(xiàn)是被 RLock() 鎖住捆探,自己也可以讀取。(這個是用數(shù)字的原子操作來控制的站粟,原理見附的文章的源碼解釋)
  • 不要嵌套地去用 黍图,這樣則有可能發(fā)生死鎖,即大家(所有 goroutine)都在等待鎖的釋放奴烙,此時發(fā)生死鎖助被。
鎖的存儲結(jié)構(gòu)圖.jpg
流程圖.jpg

參考附:

注: 測試時注意 goroutine 的 panic 可能還沒發(fā)生,主程序就退出了(goroutine 的 panic 發(fā)生時切诀,會導致主程序也退出并輸出 panic 信息)揩环。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市幅虑,隨后出現(xiàn)的幾起案子丰滑,更是在濱河造成了極大的恐慌,老刑警劉巖倒庵,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件褒墨,死亡現(xiàn)場離奇詭異炫刷,居然都是意外死亡,警方通過查閱死者的電腦和手機郁妈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門浑玛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人噩咪,你說我怎么就攤上這事顾彰。” “怎么了剧腻?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵拘央,是天一觀的道長。 經(jīng)常有香客問我书在,道長灰伟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任儒旬,我火速辦了婚禮栏账,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘栈源。我一直安慰自己挡爵,他們只是感情好,可當我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布甚垦。 她就那樣靜靜地躺著茶鹃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪艰亮。 梳的紋絲不亂的頭發(fā)上闭翩,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天,我揣著相機與錄音迄埃,去河邊找鬼疗韵。 笑死,一個胖子當著我的面吹牛侄非,可吹牛的內(nèi)容都是我干的蕉汪。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼逞怨,長吁一口氣:“原來是場噩夢啊……” “哼者疤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起叠赦,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤宛渐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體窥翩,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡业岁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了寇蚊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片笔时。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖仗岸,靈堂內(nèi)的尸體忽然破棺而出允耿,到底是詐尸還是另有隱情,我是刑警寧澤扒怖,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布较锡,位于F島的核電站,受9級特大地震影響盗痒,放射性物質(zhì)發(fā)生泄漏蚂蕴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一俯邓、第九天 我趴在偏房一處隱蔽的房頂上張望骡楼。 院中可真熱鬧,春花似錦稽鞭、人聲如沸鸟整。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽篮条。三九已至,卻和暖如春吩抓,著一層夾襖步出監(jiān)牢的瞬間涉茧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工琴拧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人嘱支。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓蚓胸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親除师。 傳聞我的和親對象是個殘疾皇子沛膳,可洞房花燭夜當晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容