一直在看golang的一些關于context包的使用的文章。我最近在博客中看到以下文章:http : //p.agnihotry.com/post/understanding_the_context_package_in_golang/
這篇文章對 go 中的背景關系取消功能做了以下說明:
“如果你愿意,你可以傳遞取消函式,但是,強烈不推薦這樣做。這可能導致取消的呼叫者沒有意識到取消背景關系的下游影響可能是什么。可能還有其他的背景關系被派生這可能會導致程式以意想不到的方式運行。簡而言之,永遠不要繞過取消功能。”
但是,如果我希望激活父 context.Done() 通道,將取消函式作為引數傳遞似乎是唯一的選擇(請參閱下面的代碼片段)。例如,以下代碼片段中的代碼 Done 通道僅在執行 function2 時激活。
package main
import (
"context"
"fmt"
"time"
)
func function1(ctx context.Context) {
_, cancelFunction := context.WithCancel(ctx)
fmt.Println("cancel called from function1")
cancelFunction()
}
func function2(ctx context.Context, cancelFunction context.CancelFunc) {
fmt.Println("cancel called from function2")
cancelFunction()
}
func main() {
//Make a background context
ctx := context.Background()
//Derive a context with cancel
ctxWithCancel, cancelFunction := context.WithCancel(ctx)
go function1(ctxWithCancel)
time.Sleep(5 * time.Second)
go function2(ctxWithCancel, cancelFunction)
time.Sleep(5 * time.Second)
// Done signal is only received when function2 is called
<-ctxWithCancel.Done()
fmt.Println("Done")
}
那么,傳遞這個取消函式實際上是一個問題嗎?是否有與使用背景關系包及其取消功能相關的最佳實踐?
uj5u.com熱心網友回復:
在您的具體示例中,代碼量足夠小,因此理解它的作業原理可能沒有問題。當您更換function1并function2使用更復雜的東西時,問題就開始了。您鏈接到的文章給出了為什么傳遞取消背景關系可以做一些難以推理的事情的具體原因,但更一般的原則是您應該嘗試將協調作業(取消、啟動 goroutines)與底層作業分開完成(無論function1和function2正在做的),盡可能地。這只是有助于更容易地獨立推理代碼的子部分,并有助于使測驗更容易。“ function2dos <something>”比“ function2dos <something> ”更容易理解function1”。
function2您可以在生成的 goroutune 中呼叫它,而不是將取消函式傳遞給function2:
func main() {
//...
go func() {
function2(ctxWithCancel)
cancelFunction()
}()
//...
}
這是小問題,因為確定何時取消的協調作業全部包含在呼叫函式中,而不是分散到多個函式中。
如果你想function2有條件地取消背景關系,讓它顯式回傳某種值,指示是否發生了一些可取消的條件:
func function2(ctx context.Context) bool {
//...
if workShouldBecanceled() {
return true
}
//...
return false
}
func main() {
//...
go func() {
if function2(ctxWithCancel) {
cancelFunction()
}
}()
//...
}
在這里我使用了一個布林值,但通常這個模式與errors 一起使用 - 如果function2回傳一個非 nil error,則取消其余的作業。
根據你在做什么,類似的東西errgroup.WithContext可能對你有用。這可以協調多個并發操作,所有這些操作都可能失敗,并在第一個失敗后立即取消其他操作。
我嘗試遵循背景關系取消的另一種最佳實踐:始終確保在某個時刻呼叫取消函式。從docs,呼叫取消函式兩次是安全的,所以我經常做這樣的事情:
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
//...
if shouldCancel() {
cancel()
}
//...
}
編輯以回應評論:
如果您有多個長時間運行的操作(例如,服務器、連接等),并且您想在第一個操作停止后立即關閉所有操作,則背景關系取消是一種合理的方法。但是,我仍然建議您在單個函式中處理所有背景關系互動。像這樣的事情會起作用:
func operation1(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
default:
}
//...
}
}
func operation2(ctx context.Context) {
// Similar code to operatoin1()
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
defer cancel()
operation1(ctx)
}()
go func() {
defer wg.Done()
defer cancel()
operation2(ctx)
}()
wg.Wait()
}
一旦其中一個操作終止,另一個操作就會被取消,但main仍會等待兩者都完成。這兩個操作都不需要擔心管理這個。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/362126.html
