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