欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

golang自帶的死鎖檢測(cè)并非銀彈的問題小結(jié)

 更新時(shí)間:2025年01月06日 09:20:45   作者:apocelipes  
Go語言自帶的死鎖檢測(cè)機(jī)制并不萬能,它只對(duì)用戶創(chuàng)建的協(xié)程進(jìn)行檢測(cè),并且在特定條件下可能會(huì)“失靈”,死鎖檢測(cè)的觸發(fā)時(shí)機(jī)和檢測(cè)內(nèi)容也有限制,因此不能完全避免死鎖問題,為了預(yù)防死鎖,應(yīng)該在編寫代碼時(shí)提前進(jìn)行設(shè)計(jì)和測(cè)試,感興趣的朋友跟隨小編一起看看吧

網(wǎng)上總是能看到有人說go自帶了死鎖檢測(cè),只要有死鎖發(fā)生runtime就能檢測(cè)到并及時(shí)報(bào)錯(cuò)退出,因此go不會(huì)被死鎖問題困擾。

這說明了口口相傳知識(shí)的有效性是日常值得懷疑的,同時(shí)也再一次證明了沒有銀彈這句話的含金量。

這個(gè)說法的殺傷力在于它雖然不對(duì),但也不是全錯(cuò),真真假假很容易讓人失去判斷力。

死鎖檢測(cè)失靈

死鎖我就不多解釋了,我們先來看個(gè)簡(jiǎn)單例子:

package main
import (
    "fmt"
)
func main() {
    c := make(chan int, 1)
    fmt.Println(<-c)
}

這段代碼會(huì)觸發(fā)golang的死鎖報(bào)錯(cuò):

fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
    /tmp/deadlock.go:9 +0x32
exit status 2

這個(gè)例子為啥鎖死了,因?yàn)闆]人給chan發(fā)數(shù)據(jù),所以接收端永久阻塞在接收操作上了。

這說明了go確實(shí)有死鎖檢測(cè)。只不過你要是覺得它什么樣的死鎖都檢測(cè)到那就大錯(cuò)特錯(cuò)了:

package main
import (
    "fmt"
    "time"
)
func main() {
    c := make(chan int, 1)
    for {
        go func() {
            fmt.Println(<-c)
        }()
        time.Sleep(10 * time.Millisecond)
    }
}

根據(jù)示例1我們可以知道如果一個(gè)chan沒有發(fā)送者,那么所有的接收者都會(huì)阻塞,在我們的例子里這些協(xié)程是永久阻塞的,理論上應(yīng)該會(huì)被檢測(cè)到然后報(bào)錯(cuò)。

遺憾的是這個(gè)程序會(huì)持續(xù)運(yùn)行下去,直到內(nèi)存耗盡為止:

死鎖檢測(cè)是有足夠的時(shí)間執(zhí)行的,因?yàn)?0毫秒雖然對(duì)人類來說短的可以忽略但對(duì)golang運(yùn)行時(shí)來說相當(dāng)漫長(zhǎng),而且我們?cè)诓煌?chuàng)建協(xié)程,滿足所有觸發(fā)檢測(cè)的條件,具體條件后面會(huì)細(xì)說。

從實(shí)驗(yàn)對(duì)照的角度來說,這時(shí)合理的猜測(cè)應(yīng)該是會(huì)不會(huì)主協(xié)程被特殊處理了,因?yàn)樯厦娴睦永镒訁f(xié)程全部死鎖,但主協(xié)程并沒有。所以我們?cè)俅芜M(jìn)行測(cè)試:

package main
import (
    "fmt"
    "time"
)
func main() {
    c := make(chan int, 1)
    go func() {
        for {
            fmt.Println("Hello from child.")
            time.Sleep(100 * time.Millisecond)
        }
    }()
    <-c
}

這段代碼同樣不會(huì)報(bào)錯(cuò),程序會(huì)持續(xù)輸出Hello直到你手動(dòng)終止進(jìn)程或者關(guān)機(jī)為止。這正說明了runtime不會(huì)在死鎖檢測(cè)上特殊對(duì)待主協(xié)程。

看上去go的死鎖檢測(cè)時(shí)常“失靈”,這是一件很恐怖的事情,尤其是在你信了文章開頭那個(gè)說法在代碼里放飛自我認(rèn)為只要沒報(bào)錯(cuò)就是沒問題之后。

go的死鎖檢測(cè)到底檢測(cè)了什么

說這是“失靈”其實(shí)有失偏頗,上面的現(xiàn)象解釋起來其實(shí)很簡(jiǎn)單,三兩段話就能說明白。

首先我們可以把go里的協(xié)程分為兩大類,一類是runtime自己的協(xié)程,包括sysmon和gc;另一類是用戶創(chuàng)建的協(xié)程,包括用戶自己創(chuàng)建的,用戶使用的第三方庫(kù)/標(biāo)準(zhǔn)庫(kù)創(chuàng)建的所有協(xié)程。我們暫且管后者叫“用戶協(xié)程”。這只是很粗糙的分類,實(shí)際的代碼中有不少出入,不過作為抽象概率幫助理解是沒問題的。死鎖檢測(cè)針對(duì)的就是“用戶協(xié)程”。

知道了檢測(cè)范圍,我們還需要知道檢測(cè)內(nèi)容——換句話說,什么情況下能判斷一組協(xié)程死鎖了?理想中當(dāng)然是檢測(cè)到一組協(xié)程循環(huán)等待某些條件或者阻塞在一些永遠(yuǎn)不會(huì)有數(shù)據(jù)的chan上?,F(xiàn)實(shí)是go只檢測(cè)這些:

  • 是否有協(xié)程處于運(yùn)行狀態(tài),包括并未實(shí)際運(yùn)行在等待調(diào)度的“可運(yùn)行”用戶協(xié)程;
  • 沒有上述條件的協(xié)程就檢測(cè)是否還有未觸發(fā)的定時(shí)器;
  • 都不滿足才會(huì)觸發(fā)死鎖報(bào)錯(cuò)并終止程序。

檢測(cè)的時(shí)機(jī)其實(shí)也是有些反直覺的,go只在創(chuàng)建/退出操作系統(tǒng)級(jí)別的線程、這些線程變?yōu)榭臻e狀態(tài)時(shí)、sysmon檢測(cè)到程序處于空閑時(shí)才會(huì)執(zhí)行死鎖檢測(cè)。也就是說,觸發(fā)檢測(cè)其實(shí)和操作系統(tǒng)線程相關(guān)性更強(qiáng)而不是和goroutine。

所以,只要還有一個(gè)協(xié)程能繼續(xù)運(yùn)行,哪怕其他99999個(gè)協(xié)程都鎖地死死得,go的死鎖檢測(cè)依然不會(huì)報(bào)錯(cuò)(更正確的說法是只要還有一個(gè)能繼續(xù)運(yùn)行的系統(tǒng)級(jí)線程,那就不算死鎖,這樣才能解釋為什么有還未觸發(fā)的定時(shí)器以及在等待系統(tǒng)調(diào)用也不算死鎖)。這樣解釋了為什么示例2和3都能運(yùn)行,因?yàn)?中主協(xié)程能正常運(yùn)行,3中子協(xié)程能正常運(yùn)行,因此其他的協(xié)程鎖死了也不會(huì)報(bào)錯(cuò)。

檢測(cè)還有兩個(gè)例外:

  • cgo管不了,因此go程序調(diào)用的c/c++代碼的線程里鎖死了go這邊也沒有辦法
  • 把go代碼編譯成c庫(kù)之后死鎖檢測(cè)會(huì)主動(dòng)關(guān)閉,因?yàn)槿绻鹀/c++代碼沒調(diào)用庫(kù)里的函數(shù)的話,那就只有runtime協(xié)程存在,這時(shí)候檢測(cè)會(huì)發(fā)現(xiàn)根本沒有用戶協(xié)程,這種檢測(cè)沒有意義。所以在這種情況下哪怕go代碼真的全部死鎖了也不會(huì)檢測(cè)到。

有人估計(jì)覺得這是bug或者設(shè)計(jì)失誤要急著去提issue了,但這不是bug!這只是看待問題的方式不同。

go采用的做法,比較正式的描述是“在期望時(shí)間內(nèi)程序的運(yùn)行是否能取得進(jìn)展”,這里的進(jìn)展當(dāng)然是指的是否在運(yùn)行或者有定時(shí)器/io要處理。以此標(biāo)準(zhǔn)只要還有用戶協(xié)程能動(dòng),那說明“整個(gè)程序”并沒有死鎖——go里判斷要不要觸發(fā)死鎖報(bào)錯(cuò)是以整個(gè)程序作為基準(zhǔn)的,而我們通常的判斷基準(zhǔn)是所有用戶協(xié)程都能在“期望時(shí)間內(nèi)獲得進(jìn)展”才是沒有問題。后者的要求更嚴(yán)格。

而且滿足后者的檢測(cè)實(shí)現(xiàn)起來很復(fù)雜,預(yù)計(jì)也會(huì)花費(fèi)非常多的計(jì)算資源,從維護(hù)和運(yùn)行性能的角度來說想做也不是很現(xiàn)實(shí)。所以go選擇了前者,前者雖然不能處理所有的問題,但仍然能在早期階段防止出現(xiàn)一部分死鎖問題。

然而把死鎖檢測(cè)當(dāng)成萬金油保險(xiǎn)絲的人就要倒霉了:

  • 現(xiàn)實(shí)的項(xiàng)目中出現(xiàn)一次性鎖死整個(gè)程序的情況其實(shí)是比較少的,更多的時(shí)間是像例子中那樣一部分協(xié)程鎖死;
  • 鎖死的協(xié)程除了不能繼續(xù)運(yùn)行之外,還會(huì)造成協(xié)程泄漏,更要命的是協(xié)程持有的對(duì)象都不會(huì)釋放,所以還伴隨著內(nèi)存泄漏;
  • 在一些程序里系統(tǒng)的一部分鎖死了可能在短時(shí)間內(nèi)影響不到其他部分,在web應(yīng)用中很常見,這會(huì)讓問題發(fā)生難以察覺,往往當(dāng)你意識(shí)到出問題時(shí)整個(gè)程序已經(jīng)到萬劫不復(fù)的狀態(tài)了。

所以死鎖檢測(cè)只能偶爾幫你一次,并不能當(dāng)成救命稻草用。

死鎖檢測(cè)的源代碼在"src/runtime/proc.go"的checkdead函數(shù)里,感興趣的可以自行把玩。

怎么檢測(cè)死鎖

既然報(bào)錯(cuò)不能料理所有情況,我們還能借助哪些工具定位是否有死鎖發(fā)生呢?

其實(shí)沒啥好辦法,下面每一種方案都需要經(jīng)驗(yàn)以及結(jié)合實(shí)際代碼才能判斷出結(jié)果。

第一種是觀察協(xié)程數(shù)量或者內(nèi)存占用是否異常。比如你的程序正常需要1000個(gè)協(xié)程,那么2千個(gè)協(xié)程也許不是出問題了,但出現(xiàn)2萬個(gè)協(xié)程那肯定是不對(duì)勁的。內(nèi)存占用同理。

這些數(shù)據(jù)很好獲取,不管是go自帶的pprof還是trace,或者是第三方的性能監(jiān)控,都能很輕松的探測(cè)到異常。難的是如何定位具體的問題。不過這節(jié)說的是如何發(fā)現(xiàn)死鎖,所以出現(xiàn)上述異常后把可能存在死鎖放進(jìn)排查方向里也就夠了。因?yàn)閰f(xié)程泄漏雖然不一定都是死鎖造成的,但死鎖最直接的表現(xiàn)就是協(xié)程泄漏。

方案1的缺點(diǎn)也很明顯,如果死鎖的協(xié)程數(shù)量固定,或者產(chǎn)生死鎖協(xié)程的速度很慢,那么監(jiān)控?cái)?shù)據(jù)上很難發(fā)現(xiàn)問題。我們也不可能簡(jiǎn)單地用服務(wù)沒響應(yīng)了來判斷是不是出了死鎖,無響應(yīng)的原因?qū)嵲谑翘嗔恕?/p>

此外uber開發(fā)的用于檢測(cè)協(xié)程泄漏的庫(kù)goleak也可以幫上一些忙,不過缺點(diǎn)是一樣的。

第二種是用go trace或者調(diào)試器dlv看運(yùn)行時(shí)的協(xié)程棧。如果棧里出現(xiàn)很多l(xiāng)ock類函數(shù)或者chan收發(fā)函數(shù),那么存在死鎖協(xié)程的概率是比較大的。最重要的是要看不同協(xié)程的調(diào)用棧里是否存在循環(huán)依賴或者交叉加鎖的情況。

方案2的缺點(diǎn)也很明顯,第一個(gè)是需要在程序運(yùn)行時(shí)獲取調(diào)用棧信息,這會(huì)影響程序的性能,協(xié)程越多影響越明顯;第二是分析調(diào)用棧現(xiàn)在沒啥好的自動(dòng)化工具往往得程序員自己上陣,如果協(xié)程數(shù)目巨大的話分析會(huì)變得極度困難。

而且調(diào)用棧里lock函數(shù)多不代表一定有死鎖,也可能只是鎖競(jìng)爭(zhēng)激烈而已。

最后一種方案是借助go trace工具,trace里有個(gè)叫block profile的,這個(gè)可以統(tǒng)計(jì)哪些函數(shù)被阻塞住了。如果看到里面有大量的lock、select相關(guān)函數(shù)、chan相關(guān)操作函數(shù),那么死鎖的可能性很大。

但和方案2一樣,方案3依然不能100%確定存在問題,還是要結(jié)合實(shí)際代碼做分析。而且trace只能分析某個(gè)時(shí)間段內(nèi)的程序運(yùn)行情況,如果你的程序死鎖問題是偶發(fā)的,那么很可能抓幾百次trace數(shù)據(jù)都不一定能抓到案發(fā)現(xiàn)場(chǎng)。

最后結(jié)論就是沒有銀彈。所以與其期待有個(gè)萬能檢測(cè)器不如寫代碼的時(shí)候就提前預(yù)防問題發(fā)生。

總結(jié)

我之所以寫這篇文章是因?yàn)楹芏鄃olang的布道師居然會(huì)以golang有死鎖檢測(cè)所以能避免死鎖錯(cuò)誤為賣點(diǎn)宣傳go語言,信以為真的go用戶也不少。稍加實(shí)驗(yàn)就能證偽的說法如今依然大行其道,令人感嘆。

軟件行業(yè)是很難出現(xiàn)銀彈的,因此多動(dòng)手檢驗(yàn)才能少踩坑早下班。

到此這篇關(guān)于golang自帶的死鎖檢測(cè)并非銀彈的文章就介紹到這了,更多相關(guān)golang死鎖檢測(cè)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • go語言實(shí)現(xiàn)順序存儲(chǔ)的棧

    go語言實(shí)現(xiàn)順序存儲(chǔ)的棧

    這篇文章主要介紹了go語言實(shí)現(xiàn)順序存儲(chǔ)的棧,實(shí)例分析了Go語言實(shí)現(xiàn)順序存儲(chǔ)的棧的原理與各種常見的操作技巧,需要的朋友可以參考下
    2015-03-03
  • Go語言時(shí)間管理利器之深入解析time模塊的實(shí)戰(zhàn)技巧

    Go語言時(shí)間管理利器之深入解析time模塊的實(shí)戰(zhàn)技巧

    本文深入解析了Go語言標(biāo)準(zhǔn)庫(kù)中的time模塊,揭示了其高效用法和實(shí)用技巧,通過學(xué)習(xí)time模塊的三大核心類型(Time、Duration、Timer/Ticker)以及高頻使用場(chǎng)景,開發(fā)者可以更好地處理時(shí)間相關(guān)的任務(wù),感興趣的朋友一起看看吧
    2025-03-03
  • Ruby序列化和持久化存儲(chǔ)(Marshal、Pstore)操作方法詳解

    Ruby序列化和持久化存儲(chǔ)(Marshal、Pstore)操作方法詳解

    這篇文章主要介紹了Ruby序列化和持久化存儲(chǔ)(Marshal、Pstore)操作方法詳解,包括Ruby Marshal序列化,Ruby Pstore存儲(chǔ),需要的朋友可以參考下
    2022-04-04
  • Go語言字符串操作指南:簡(jiǎn)單易懂的實(shí)戰(zhàn)技巧

    Go語言字符串操作指南:簡(jiǎn)單易懂的實(shí)戰(zhàn)技巧

    本文將介紹Go語言中字符串的實(shí)戰(zhàn)操作,通過本文的學(xué)習(xí),讀者將掌握Go語言中字符串的常用操作,為實(shí)際開發(fā)提供幫助,需要的朋友可以參考下
    2023-10-10
  • go語言?nil使用避坑指南

    go語言?nil使用避坑指南

    這篇文章主要為大家介紹了go語言?nil使用避坑指南詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • 解析Golang中的鎖競(jìng)爭(zhēng)問題

    解析Golang中的鎖競(jìng)爭(zhēng)問題

    這篇文章主要介紹了golang中的鎖競(jìng)爭(zhēng)問題,本文通過實(shí)例代碼給大家詳細(xì)講解,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-10-10
  • 利用go-kit組件進(jìn)行服務(wù)注冊(cè)與發(fā)現(xiàn)和健康檢查的操作

    利用go-kit組件進(jìn)行服務(wù)注冊(cè)與發(fā)現(xiàn)和健康檢查的操作

    這篇文章主要介紹了利用go-kit組件進(jìn)行服務(wù)注冊(cè)與發(fā)現(xiàn)和健康檢查的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • Golang中slice刪除元素的性能對(duì)比

    Golang中slice刪除元素的性能對(duì)比

    go沒有對(duì)刪除切片元素提供專用的語法或者接口,需要使用切片本身的特性來刪除元素,下面這篇文章主要給大家介紹了關(guān)于Golang中slice刪除元素的性能對(duì)比,需要的朋友可以參考下
    2022-06-06
  • Go語言切片常考的面試真題解析

    Go語言切片??嫉拿嬖囌骖}解析

    了解最新的Go語言面試題型,讓面試不再是難事,下面這篇文章主要給大家介紹了關(guān)于Go語言切片面試??嫉囊恍﹩栴},文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-02-02
  • 深入了解Go語言中g(shù)oioc框架的使用

    深入了解Go語言中g(shù)oioc框架的使用

    goioc?是一個(gè)基于?GO?語言編寫的依賴注入框架,基于反射來進(jìn)行編寫。本文主要為大家介紹了goioc框架的原理與使用,需要的可以參考一下
    2022-11-11

最新評(píng)論