一、 Context 的正確使用與底層原理
1.結構體
type Context interface {// Deadline 返回此 Context 被取消的時間點。// 如果未設置截止時間,ok 為 false。Deadline() (deadline time.Time, ok bool)// Done 返回一個 channel。當 Context 被取消或超時后,此 channel 會被關閉。// 這是一個只讀的 channel。關閉的 channel 仍然可以讀取,會立即返回零值。// 通過 select 語句監聽 Done channel 的關閉,是接收取消信號的關鍵。Done() <-chan struct{}// Err 返回 Context 為何被取消的錯誤。// 如果 Done channel 還未被關閉,返回 nil。// 如果已被關閉,返回非 nil 的錯誤解釋原因:context.Canceled 或 context.DeadlineExceeded。Err() error// Value 允許 Context 攜帶請求域的數據。// 這些數據必須是安全的,以供多個 Goroutine 同時使用。// 應該用于傳遞非必要的、過渡性的數據,而非函數的可選參數。Value(key any) any
}
2.根與派生
創建根上下文
context.Background()
context.TODO()
派生(包裝)現有上下文
WithCancel 是基礎的可取消上下文包裝,返回的cancel,調用后即可以立刻取消包裝后的上下文,以及其派生上下文(不會影響到父上下文)
WithDeadline,WithTimeout是帶截止時間/時間間隔的可取消上下文包裝
WithValue 是包裝消息,一般是一些貫穿全文的必要信息比如:用戶id, token等
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val any) Context
3.取消上下文原理:
以 func WithCancel(parent Context) (ctx Context, cancel CancelFunc) 為例:
3.1封裝的 ctx有方法 Done() <-chan struct{},調用cancel后會給 Done() 的通道發送消息。監聽這個通道的其他協程收到消息就可以返回(一般通過 select case default)。
3.2 構建關聯(propagateCancel):
這個是解決當前上下文取消后,子上下文也被取消的關鍵:
派生的上下文如果是 cancelctx, 那么他會檢查父上下文是不是也是,如果是的話,會將自己加入到 父context的 childmap,當父上下文被取消后,會遍歷childmap來取消子上下文。
(ps: 父子上下文指的是包裝前后,而不是函數傳遞。)
4. 實踐總結:
4.1 誰創建誰取消:
ctx, cancel :=WithCancel(context.Background() )
defer cancel() // 直接defer取消
4.2 Done chan 是只讀的
4.3謹慎適用 WithValue,這個一般只傳貫穿全文的必要信息比如:用戶id, token等,具體的參數 通過函數參數傳遞
4.4 context對象是不可變的(不能從普通變成cancelctx),只能包裝
4.5 context 的三大作用:取消信號,超時/截止時取消信號,WithValue存信息