問題描述
在 Go 中使用“context”包時,go vet如果不呼叫 context.WithCancel() 回傳的取消函式,命令的“lostcancel”檢查會給出警告。例如,如果您有:
func main() {
ctx, cancel := context.WithCancel(context.Background())
foo(ctx)
fmt.Println("done")
}
...然后 Go vet 會給你警告:
取消功能未在所有路徑上使用(可能的背景關系泄漏)
Context 包的檔案中也提到了這一點:
呼叫 CancelFunc 失敗會泄漏子項及其子項,直到父項被取消或計時器觸發。go vet 工具檢查是否在所有控制流路徑上使用了 CancelFuncs。
我的問題是:至少在一種情況下,我不明白為什么必須呼叫取消函式。假設我想派生一個子背景關系,以便我可以選擇:
- 在取消父級之前使用子背景關系停止下游 goroutines,例如遇到早期錯誤時,或者
- 等到父背景關系被取消,讓子背景關系與父背景關系一樣長。
在我看來,這似乎是背景關系包的父/子樹結構的一個非常自然的用例。但這會產生與go vet上面提到的相同的錯誤,因為在表示第二個選項的代碼執行路徑中(讓子項與父項的生存時間相同),不會呼叫子項背景關系的取消函式。
那么,這真的是go vetGo 社區使用 Context 指南的一個缺點,還是我對這個主題的思考存在缺陷?
示例程式
這是發生錯誤的示例程式。Go vet 會給這條線發出警告barctx, barcancel := context.WithCancel(ctx)。這個例子顯然很愚蠢,因為我可以在啟動它的 goroutine之前bar檢查錯誤。但我只想說:在我的實際程式中,這不是一個選擇。
func main() {
ctx, cancel := context.WithCancel(context.Background())
foo(ctx)
cancel()
time.Sleep(10 * time.Millisecond)
fmt.Println("done")
}
func foo(ctx context.Context) {
go func() {
<-ctx.Done()
fmt.Println("foo done")
}()
for i := 0; i < 2; i {
barctx, barcancel := context.WithCancel(ctx)
err := bar(barctx, i)
if err != nil {
fmt.Println("bar error; canceled")
barcancel()
}
}
}
func bar(ctx context.Context, x int) (err error) {
go func() {
<-ctx.Done()
fmt.Printf("bar done; x=%d\n", x)
}()
if x > 0 {
err = fmt.Errorf("bar error")
}
return err
}
uj5u.com熱心網友回復:
在您的示例中,go vet回傳警告是正確的。問題具體在于bar功能。當你創建它時,創建一個阻塞直到或被呼叫bar的 goroutine 。在那之前,它將繼續存在。因此,您必須呼叫取消函式,以便等待背景關系完成的資源可以超出范圍并被垃圾收集。barcancelcancel
需要明確的是,在您的示例中,所有延遲的例程都將被取消,因為您取消了ctx,這也取消了barctx. 但是,您不應該依賴它,因為您可能無法控制是否取消父背景關系。因此,您的函式應該對其在函式呼叫中創建和使用的背景關系負責。
為了清楚起見,您應該像這樣修改示例代碼:
barctx, barcancel := context.WithCancel(ctx)
err := bar(barctx, i)
if err != nil {
fmt.Println("bar error; canceled")
}
barcancel()
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/530854.html
標籤:去去上下文
上一篇:具有兩個或多個模型的GORM查詢
下一篇:如何從游標位置向下清除終端螢屏?
