Go 中的並發性是以 goroutine(獨立活動)和 channel(用於通信)的形式實現的。處理 goroutine 時,程式設計師需要小心翼翼地避免泄露。如果最終永遠堵塞在 I/O 上(例如 channel 通信),或者陷入死循環,那麼 goroutine 會發生泄露。即使是阻塞的 goroutine,也會消耗資源,因此,程序可能會使用比實際需要更多的內存,或者最終耗盡內存,從而導致崩潰。讓我們來看看幾個可能會發生泄露的例子。然後,我們將重點關注如何檢測程序是否受到這種問題的影響。
假設出於冗餘的目的,程序發送請求到許多後端。使用首先收到的響應,丟棄後面的響應。下面的代碼將會通過等待隨機數毫秒,來模擬向下游伺服器發送請求:
輸出:
每次調用 queryAll 後,goroutine 的數目會發生增長。問題在於,在接收到第一個響應後,「較慢的」 goroutine 將會發送到另一端沒有接收者的 channel 中。
可能的解決方法是,如果提前知道後端伺服器的數量,那麼使用緩存 channel。否則,只要至少有一個 goroutine 仍在工作,我們就可以使用另一個 goroutine 來接收來自這個 channel 的數據。其他的解決方案可能是使用 context(example),利用 某些機制來取消其他請求。
這種場景類似於發送到一個沒有接收者的 channel。泄露 goroutine 這篇文章中包含了一個示例。
寫入到 nil channel 會永遠阻塞:
所以它導致死鎖:
當從 nil channel 讀取數據時,同樣的事情發生了:
當傳遞尚未初始化的 channel 時,也可能會發生:
在這個例子中,有一個顯而易見的罪魁禍首 —— if false {,但是在更大的程序中,更容易忘記這件事,然後使用 channel 的零值(nil)。
goroutine 泄露不僅僅是因為 channel 的錯誤使用造成的。泄露的原因也可能是 I/O 操作上的堵塞,例如發送請求到 API 伺服器,而沒有使用超時。另一種原因是,程序可以單純地陷入死循環中。
分析
簡單的方式是使用由 runtime.NumGoroutine 返回的值。
net/http/pprof
調用 http://localhost:6060/debug/pprof/goroutine?debug=1 ,將會返回帶有堆棧跟蹤的 goroutine 列表。
要將現有的 goroutine 的堆棧跟蹤列印到標準輸出,請執行以下操作:
gops
集成到你的程序中:
leaktest
這是用測試來自動檢測泄露的方法之一。它基本上是在測試的開始和結束的時候,利用 runtime.Stack 獲取活躍 goroutine 的堆棧跟蹤。如果在測試完成後還有一些新的 goroutine,那麼將其歸類為泄露。
分析甚至已經在運行的程序的 goroutine 管理,以避免可能會導致內存不足的泄露,這至關重要。代碼在生產上運行數日後,這樣的問題通常就會出現,因此它可能會造成真正的損害。
點擊原文中的 以幫助其他人發現這個問題。如果你想實時獲得新的更新,請關注原作者哦~
資源
via: https://medium.com/golangspec/goroutine-leak-400063aef468
作者:Michał Łowicki 譯者:ictar 校對:polaris1119
本文由 GCTT 原創編譯,Go語言中文網 榮譽推出