這篇文章詳細介紹了Go 語言中context 函式背後的實現細節和程式碼,幫助開發人員瞭解上下文包的底層工作原理。
我們來看一個使用 context 包的簡單示例:該函式接受一個上下文並將其傳遞給另一個函式,因為對於大多數人來說,這就是上下文的全部,只是在函式需要時傳遞的東西。
func main() { |
將列印出 context.Background。這是因為 context.Background 返回的內容滿足 Stringer 介面的要求,而 Stringer 介面在呼叫 String.Background 時只會返回該內容。
context介面
讓我們從最基本的開始。我們使用的 context.Context 型別是一個介面,下面是它的定義。
type Context interface { |
任何滿足此介面的結構都是有效的上下文物件。如果註釋中沒有說明,讓我們快速瞭解一下它們分別是什麼。
- Deadline:該函式返回設定為截止日期的時間,例如,如果上下文是使用 context.WithDeadline 建立的。
- Done:該函式返回一個在取消上下文時關閉的通道。
- Err:如果取消已發生,則返回非零。
- Value:值:該函式用於獲取儲存在上下文例項中的值。
如果你想建立一個 "Context",這些就是你所需要的,而且你可以很容易地建立它們。儘管如此,stdlib 還是為我們提供了一些有用的 "上下文"。
emptyCtx 結構
這是一個結構體,滿足成為 Context 的最基本要求。程式碼如下
type emptyCtx struct{} |
如您所見,它什麼也不做,但這就是 context.Background 和 context.TODO 中的主要內容。
context.Background 和 context.TODO
這兩種方法都只是 emptyCtx 加上一個 String 方法,以滿足 Stringer 介面的要求。它們提供了一種建立空基礎上下文的方法。它們之間唯一的區別就是名稱不同。
- 當你知道需要一個空上下文時,比如在剛剛開始執行的 main 中,你可以使用 context.Background;
- 當你不知道使用什麼上下文或還沒有接好線時,你可以使用 context.TODO。
您可以將 context.TODO 視為類似於在程式碼中新增 // TODO 註釋。
context.Background:
type backgroundCtx struct{ emptyCtx } |
context.TODO:
type todoCtx struct{ emptyCtx } |
context.WithValue
現在,我們將進入 context 軟體包的更多實用案例。如果想使用 context 傳遞一個值,可以使用 context.WithValue。您可能見過日誌或網路框架使用這種方法。
讓我們看看它的內部結構:
type valueCtx struct { |
它只是返回一個包含父上下文、鍵和值的結構體。
如果你注意到,該例項只能包含一個鍵和一個值,但你可能在網路框架中看到過,它們會從 ctx 引數中提取多個值。由於我們將父上下文嵌入到了較新的上下文中,因此可以向上遞迴搜尋以獲取其他任何值。
bgCtx := context.Background() |
現在,如果我們要從 v2Ctx 中獲取 "one "的值,可以呼叫 v2Ctx.Value("one")。
這將首先檢查 v2Ctx 中的鍵是否為 "one",
如果不是,則將檢查父節點(v1Ctx)的鍵是否為 "one"。
既然 v1Ctx 中的鍵是 "one",我們就返回上下文中的值。
下面程式碼遞迴搜尋父上下文,檢視其中是否有匹配鍵的上下文,然後返回其值。
func (c *valueCtx) Value(key any) any { |
context.WithCancel
讓我們來看看更有用的東西。您可以使用上下文包建立一個 ctx,用來向下遊函式發出取消訊號。
讓我們來看一個如何使用的示例:
func doWork(ctx context.Context) { |
在這種情況下,您可以透過在主函式中呼叫 cancel 來向 doWork 函式發出停止工作的訊號。
現在來看看它是如何工作的。讓我們從函式定義開始(我們很快就會講到結構定義):
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { |
當您呼叫 context.WithCancel 時,它會返回一個 cancelCtx 例項和一個函式,您可以呼叫該函式來取消上下文。由此我們可以推斷,cancelCtx 是一個具有取消函式的上下文,該函式可用來 "取消 "上下文。
如果你忘記了,取消上下文只是意味著關閉 Done() 返回的通道。
順便說一下,在此上下文中,propagateCancel 函式的主要作用是建立一個 cancelCtx,以及在建立之前確保父節點尚未被取消。
好了,現在讓我們來看看結構圖,之後我們將瞭解它是如何工作的(source).
type cancelCtx struct { |
這裡:
- Context:儲存父上下文
- mu sync.Mutex:你知道這是什麼吧
- done atomic.Value:儲存將由 Done() 函式返回的 chan struct{}
- err error:儲存導致取消的錯誤資訊
- cause error:儲存取消的原因,即取消函式的最後一個引數
對了,這是取消功能(source):
func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) { |
首先,我們在例項上設定原因和錯誤,然後關閉 Done 返回的通道。之後,它會取消所有子例項,最後將自己從父例項中移除。
Context.WithDeadline 和context.WithTimeout
當您想建立一個在到達截止日期時自動取消的上下文時,這些功能就非常有用。這對於執行伺服器超時等操作非常有用。
首先,context.WithTimeout 只是計算截止時間並呼叫 context.WithDeadline。事實上,這就是它的全部程式碼:
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { |
現在進入細節部分。有些人可能已經猜到,WithDeadline 基本上就是一個普通的 WithCancel 上下文,只不過是由上下文包來處理取消。
讓我們看看程式碼的作用。這裡有函式 WithDeadlineCause 的程式碼,它是 WithDeadline 的一個變體,但增加了傳遞取消的 "原因 "的功能。順便提一下,其他上下文包函式也可以使用 Cause 變體,而像 WithDeadline 這樣的非 Cause 變體只是在呼叫 Cause 變體時將 cause 設為 nil。
func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc) { |
- 如果父節點的截止日期早於子節點,則返回從父節點建立的簡單 cancelCtx
- 如果不是,則建立一個新的 timeCtx(結構定義如下)
- 現在檢查是否已超過截止時間,如果是,則取消已建立的上下文並返回
- 如果沒有,我們將使用 time.AfterFunc 設定一個計時器來執行取消操作,然後返回
timerCtx 只是一個帶有計時器的 cancelCtx:
type timerCtx struct { |