Go 翻山越嶺 - 內置數據結構 - Context

Go 語言在 1.16 版本之後加入了新的內置數據結構 Context,雖然在代碼中使用都比較簡單,但語言內部還是做了許多區分,今天來分析一下 Context。

Context

雖然在使用 context 的時候,看起來都是 context.* 的結構,但 Go 語言內部做了這樣一個區分:

context 操作

具體說明如下:

這裏有個例子

 package main
 
 import (
     "context"
     "fmt"
 )
 
 type orderID int
 
 func main() {
     var x = context.TODO()
     x = context.WithValue(x, orderID(1), "1234")
     x = context.WithValue(x, orderID(2), "2345")
     x = context.WithValue(x, orderID(3), "3456")
     fmt.Println(x.Value(orderID(2)))
 
 }

具體流程如下,前一個節點是後一個節點的 parent

valueCtx{k: 3, v: 3456}  →  valueCtx{ k: 2, v: 2345 } → valueCtx{ k: 1, v: 1234} → emptyCtx

看起來像是鏈表,但其實 Context 更像是一棵樹,這裏例子更直觀些:

 package main
 
 import (
     "context"
     "fmt"
 )
 
 type orderID int
 
 func main() {
     var x = context.TODO()
     x = context.WithValue(x, orderID(1), "1234")
     x = context.WithValue(x, orderID(2), "2345")
     
     y = context.WithValue(x, orderID(3), "4567")
     y = context.WithValue(x, orderID(4), "3456")
     
     fmt.Println(x.Value(orderID(3)))
     fmt.Println(y.Value(orderID(3)))
 }

x: valueCtx{k: 3, v: 3456}  →

→  valueCtx{k: 2, v: 2345} → valueCtx{ k: 1, v: 1234} → emptyCtx

y: valueCtx{k: 3, v: 4567}  →

這樣看起來就更像樹結構了。

我們在開發中可能會遇到, context 影響 goroutine,僞代碼如下:

 func () {
     go func1() {} ()
     go func2() {
         go func3() {} ()
    }
 }

其實這也是一個樹形結構,這裏做了個簡化就是:假設我們在每一個 goroutine 中都創建了和它對應的 context,那麼可以認爲我們的根就是對應的最外層的函數。內部每啓動一個 goroutine ,就會對應以下這樣的節點:

context 影響 goroutine

這種結構正好和我們程序執行的順序是匹配的,最終生成一個 context 樹。

假如我們想進一步,生成了 context 樹之後,做些取消操作:比如 goroutine 都沒執行完,不過想要取消下圖紅框所示 cancelCtx,意味着取消後,後續的子 goroutine 都需要取消,就需要調用最外部的 cancelCtx。就相當於,父節點取消時,可以傳導到所有子節點中。

context 局部取消

但這種方式也會有一種問題,context 的操作都需要在 goroutine 中去做配合的。也就是說我們在 goroutine 中一定要用 select 然後用 ctx.dump 的操作。如果不寫就相當於完全不配合外部取消操作,也就不監聽外部取消通知,理論上 goroutine 還是配合不了。所以,即便有了 context 這種侵入式用法,還是需要寫一些 goroutine 處理的相關邏輯。雖然整體上不是很好用,但相比其他語言,我能夠簡單粗暴地中斷某個執行還是很不錯的,可以不用考慮執行現場中怎麼恢復、怎麼清理當時分配的資源。

Go 語言中,不僅有這種基於 context 和 goroutine 中 select  ctx.dump 的中斷方式,在內部還能夠通過信號來中斷。而且信號中斷沒有給用戶暴露相關的手段,用戶能夠操作的也就只有常見的讓程序直接停止和退出的情況。

這其實也是一種搶佔式調度的方式,相當於可以從執行的彙編流中,任意的位置中斷。然後把現場保存,在後續流程中如果需要就恢復回來。

最後說下 context 的社區現狀。因爲 context 不是很好用,社區中不少人在罵,這種侵入方式到底好不好。據說 Go 2.0.x 以後,用實現簡單的 context 來做取消中斷還是比較方便,而信號就沒那麼方便了。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/R4sj2Hfw4x1ItUH-ucX8Ww