亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

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

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

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

死鎖檢測失靈

死鎖我就不多解釋了,我們先來看個簡單例子:

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

這段代碼會觸發(fā)golang的死鎖報錯:

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

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

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

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我們可以知道如果一個chan沒有發(fā)送者,那么所有的接收者都會阻塞,在我們的例子里這些協(xié)程是永久阻塞的,理論上應(yīng)該會被檢測到然后報錯。

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

死鎖檢測是有足夠的時間執(zhí)行的,因為10毫秒雖然對人類來說短的可以忽略但對golang運行時來說相當漫長,而且我們在不停創(chuàng)建協(xié)程,滿足所有觸發(fā)檢測的條件,具體條件后面會細說。

從實驗對照的角度來說,這時合理的猜測應(yīng)該是會不會主協(xié)程被特殊處理了,因為上面的例子里子協(xié)程全部死鎖,但主協(xié)程并沒有。所以我們再次進行測試:

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
}

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

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

go的死鎖檢測到底檢測了什么

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

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

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

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

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

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

檢測還有兩個例外:

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

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

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

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

然而把死鎖檢測當成萬金油保險絲的人就要倒霉了:

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

所以死鎖檢測只能偶爾幫你一次,并不能當成救命稻草用。

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

怎么檢測死鎖

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

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

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

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

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

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

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

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

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

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

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

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

總結(jié)

我之所以寫這篇文章是因為很多golang的布道師居然會以golang有死鎖檢測所以能避免死鎖錯誤為賣點宣傳go語言,信以為真的go用戶也不少。稍加實驗就能證偽的說法如今依然大行其道,令人感嘆。

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

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

相關(guān)文章

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

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

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

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

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

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

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

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

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

    go語言?nil使用避坑指南

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

    解析Golang中的鎖競爭問題

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

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

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

    Golang中slice刪除元素的性能對比

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

    Go語言切片常考的面試真題解析

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

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

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

最新評論