摘要
在很多的 Go 開源架構裡,我們經常能看到 context 的身影,它的使用場景有很多,像逾時通知,取消通知都用到了 context。今天我們就來好好的認識一下它,看看 context 的相關知識和底層原理。
context 介紹
context 從它的字面量就可以看出來,是用來傳遞資訊的。當然,這種傳遞并不僅僅是将資料塞給被調用者,它還能進行鍊式的傳遞,通過儲存父子 context 關系,不斷的疊代周遊來擷取資料。
除此之外,context 還能進行鍊式的傳播 channel 信号。
我們知道
channel是用來做 goroutine 通信使用的。這就使得 goroutine 之間能夠進行鍊式的信号通知了,進而達到自上而下的通知效果。
例如通知所有跟 context 有血緣關系的 goroutine 進行取消動作。
Context 接口
在 Go 裡并沒有直接為我們提供一個統一的 context 對象,而是設計了一個接口類型的 Context。然後在這些接口上來實作了幾種具體類型的 context。
這樣的好處就是我們隻要根據開放出來的接口定義,也能夠實作屬于自己的 context,進而跟官方的 context 一起配合使用。
在分析官方的幾種 context 之前,我們先來看看 context 要求實作的幾個接口:
- Deadline() (deadline time.Time, ok bool)
- Done() <-chan struct{}
- Err() error
- Value(key interface{}) interface{}
其中:
Deadline()
表示如果有截止時間的話,得傳回對應 deadline 時間;如果沒有,則 ok 的值為 false。
Done()
表示關于 channel 的資料通信,而且它的資料類型是 struct{},一個空結構體,是以在 Go 裡都是直接通過 close channel 來進行通知的,不會涉及具體資料傳輸。
Err()
傳回的是一個錯誤 error,如果上面的 Done() 的 channel 沒被 close,則 error 為 nil;如果 channel 已被 close,則 error 将會傳回 close 的原因,比如逾時或手動取消。
Value()
則是用來存儲具體資料的方法。
Context 類型
簡單的看過 Context 接口之後,我們來看看官方的 context 類型。主要有四種,分别是
emptyCtx
,
cancelCtx
timerCtx
valueCtx
:
- emptyCtx:空的 context,實作了上面的 4 個接口,但都是直接 return 預設值,沒有具體功能代碼。
- cancelCtx:用來取消通知用的 context
- timerCtx:用來逾時通知用的 context
- valueCtx:用來傳值的 context
emptyCtx 表示什麼都沒有的 context,一般用作最初始的 context,作為父 context 使用。像我們常見的
context.Background()
傳回的就是 emptyCtx。
其他類型的建立方法如下:
- WithCancel 方法建立的是 cancelCtx 類型的 context。
- WithDeadline 方法建立的是 timerCtx 類型的 context。
- WithValue 方法建立的是 valueCtx 類型的 context。
上面三個方法在建立的時候都會要求傳 parent context 進來,以此達到鍊式傳遞資訊的目的。
Context 源碼
context 的源碼在 src/context/context.go 裡,相信大家仔細研究,也能看到上面介紹的幾個 context 對象。這邊簡單解釋下
cancelCtx
、
timerCtx
valueCtx
的核心流程。
1)cancelCtx 、timerCtx(用來通知用的 context)
cancelCtx 、timerCtx 在建立的時候都會調用
propagateCancel
方法,将目前的 context 挂在 父 context 下。
接着在 Done() 方法裡傳回了對應的 channel,讓調用者能夠監聽 channel 信号。
當要執行取消動作時,會通過 cancel 方法關閉 channel,來達到通知 goroutine 的目的。
在 channel 關閉的同時也會對子 context 調用 cancel 方法,直到沒有子 context。
cancelCtx 和 timerCtxt 不同之處就在于 cancelCtx 是手動調用 cancel 方法來觸發取消通知;
而 timerCtxt 則通過 AfterFunc 逾時時間來自動觸發 cancel 方法。
2)valueCtx(用來傳值的 context)
valueCtx 通過 key-value 形式來存儲資料,當找不到 key 時,就會到 父 context 裡查找,直到沒有父 context:
func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
return c.Context.Value(key) // 到父 context 裡查找
}
context 注意事項
最後我們來看看在使用 context 時的幾個注意事項:
- context 的 Done() 方法往往需要配合 select {} 使用,以監聽退出。
- 盡量通過函數參數來暴露 context,不要在自定義結構體裡包含它。
- WithValue 類型的 context 應該盡量存儲一些全局的 data,而不要存儲一些可有可無的局部 data。
- context 是并發安全的。
- 一旦 context 執行取消動作,所有派生的 context 都會觸發取消。