Context

KevinYang發表於2020-08-23

上下文 context.Context 一般用於在 API 邊界之間以及過程之間傳遞截止時間、取消訊號或者其他請求相關的資料。

一個介面

context.Context 是 Go 語言在 1.7 版本中引入標準庫的介面,該介面定義了四個需要實現的方法,其中包括:

  1. Deadline — 返回 context.Context 被取消的時間,也就是完成工作的截止日期;
  2. Done — 返回一個 Channel,這個 Channel 會在當前工作完成或者上下文被取消之後關閉,多次呼叫 Done 方法會返回同一個 Channel;
  3. Err — 返回 context.Context 結束的原因,它只會在 Done 返回的 Channel 被關閉時才會返回非空的值;
    1. 如果 context.Context 被取消,會返回 Canceled 錯誤;
    2. 如果 context.Context 超時,會返回 DeadlineExceeded 錯誤;
  4. Value — 從 context.Context 中獲取鍵對應的值,對於同一個上下文來說,多次呼叫 Value 並傳入相同的 Key 會返回相同的結果,該方法可以用來傳遞請求特定的資料;

四個具體實現

  • emptyCtx

    本質是一個 int。
    emptyCtx 永遠不會取消,沒有值,也沒有截止日期。它不是 struct{},因為此型別的變數必須具有不同的地址。

  • cancelCtx

    type cancelCtx struct {
      Context
    
      mu       sync.Mutex  // 用於保護這幾個欄位的鎖,以保證 cancelCtx 是執行緒安全的        
      done     chan struct{}  // 用於獲取該 Conext 的取消通知     
      children map[canceler]struct{} // 用於儲存以當前節點為根節點的所有可取消的 Context,以便在根節點取消時可以把它們一併取消
      err      error  // 用於儲存取消時指定的錯誤資訊               
    }
  • timerCtx

    type timerCtx struct {
      cancelCtx
    
      timer *time.Timer // 由 cancelCtx.mu 來保護
      deadline time.Time
    }

    在 cancelCtx 的基礎上封裝了一個定時器和一個截止時間,這樣既可以根據需要主動取消,也可以在到達 deadline 是通過 timer 來觸發取消動作。

  • valueCtx

    type valueCtx struct {
      Context
    
      key, val interface{}
    }

六個函式

  • func Background() Context

  • func TODO() Context

    Background 和 TODO 這兩個函式內部都會建立 emptyCtx,
    本質上是一樣的,主要是語義上的區別。

    Background 主要用於在初始化是獲取一個 Context,而 TODO 函式官方文件建議在本來應該使用外層傳遞的 ctx,而外層卻沒有傳遞的地方使用。

  • func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

    用於把一個 Context 包裝成一個 cancelCtx,並提供一個取消函式,呼叫它可以取消對應的 Context。

  • func WithDeadline(parent Context, d time.Time) (ctx Context, cancel CancelFunc)

  • func WithTimtount(parent Context, d time.Duration) (ctx Context, cancel CancelFunc)

  • func WithValue(parent Context, key, value interface{}) Context

    用於上下文傳值。

    func (c *valueCtx) Value(key interface{}) interface{} {
      // 用子 context 取父 context 的值時,如果 key 被判等則會出現覆蓋的情況,所以最好不要直接使用 string、int 這些基礎型別作為 key,而是每一個 Context 的 keys 都要用一種自定義型別包裝一下。
      if c.key == key {
        return c.val
      }
      return c.Context.Value(key)
    }

Context 使用原則

  1. 不要把 Context 放在結構體中,要以引數的方式傳遞
  2. 以 Context 作為引數的函式方法,應該把Context作為第一個引數,放在第一位。
  3. 給一個函式方法傳遞 Context 的時候,不要傳遞 nil,如果不知道傳遞什麼,就使用 context.TODO
  4. Context 的 Value 相關方法應該傳遞必須的資料,不要什麼資料都使用這個傳遞
  5. Context 是執行緒安全的,可以放心的在多個 goroutine 中傳遞
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章