亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

Go 上下文取消函數的最佳實踐

Go 上下文取消函數的最佳實踐

Go
呼啦一陣風 2022-10-17 09:57:10
我一直在閱讀一些關于使用 golang 的 context 包的文章。我最近在博客中看到了以下文章:http: //p.agnihotry.com/post/understanding_the_context_package_in_golang/該文章對 go 中的上下文取消功能進行了以下說明:“如果你愿意,你可以傳遞取消函數,但是,強烈不建議這樣做。這可能導致取消的調用者沒有意識到取消上下文可能會對下游產生什么影響??赡苓€有其他派生的上下文這可能會導致程序以意想不到的方式運行。簡而言之,永遠不要繞過取消函數。但是,如果我希望激活父 context.Done() 通道,將取消函數作為參數傳遞似乎是唯一的選擇(請參見下面的代碼片段)。例如,下面代碼片段中的代碼 Done 通道僅在執行 function2 時才被激活。package mainimport (    "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")}那么,傳遞這個取消函數真的是個問題嗎?是否有與使用 context 包及其取消功能相關的最佳實踐?
查看完整描述

1 回答

?
慕斯709654

TA貢獻1840條經驗 獲得超5個贊

在您的具體示例中,代碼量足夠小,理解它是如何工作的可能沒有問題。當您更換function1并function2使用更復雜的東西時,問題就開始了。您鏈接到的文章給出了為什么傳遞取消上下文可以做一些難以推理的事情的具體原因,但更一般的原則是您應該嘗試將協調工作(取消,旋轉 goroutines)與底層工作分開以盡可能地完成(無論做什么function1,function2正在做什么)。這只是有助于更容易獨立地推理代碼的子部分,并有助于使測試更容易?!?do <something>”比“ do <something> function2”更容易理解function2function1”。


無需將取消函數傳遞給function2,您只需在生成的 gooutune 中調用它即可運行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仍等待兩者完成。這兩個操作都不需要擔心管理這個問題。


查看完整回答
反對 回復 2022-10-17
  • 1 回答
  • 0 關注
  • 139 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號