Go:context.Context

牛马chen發表於2024-11-24

什麼是 context

context 是 Go 標準庫中用來管理任務生命週期跨 API 資料傳遞的工具。它的主要應用場景是在併發程式設計中,尤其是處理像 HTTP 請求這樣有超時限制或需要手動取消的任務。

為了更通俗地理解,可以把 context 想象成一個任務的「管理員」,它可以:

  1. 通知任務何時結束(比如超時或取消時)。
  2. 傳遞一些全域性資訊(比如使用者身份或配置)。

從常用例子開始

1. HTTP 請求中的超時控制

假設我們正在寫一個 HTTP 伺服器,客戶端請求要求超時 2 秒:

package main

import (
	"context"
	"fmt"
	"net/http"
	"time"
)

func handler(w http.ResponseWriter, r *http.Request) {
	// 建立一個帶超時的 context,2 秒後自動取消
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel() // 確保函式結束時釋放資源

	// 模擬一個耗時任務
	select {
	case <-time.After(3 * time.Second):
		fmt.Fprintln(w, "Finished task")
	case <-ctx.Done(): // 當 context 超時時,Done 會被觸發
		http.Error(w, "Request timed out", http.StatusRequestTimeout)
	}
}

func main() {
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8080", nil)
}

執行邏輯:

  • 如果任務完成時間超過 2 秒,ctx.Done() 會收到取消訊號,及時停止任務,避免資源浪費。
  • context 幫助我們優雅地處理超時。

2. 跨 API 傳遞請求資訊

context 還能傳遞資料,比如使用者的認證資訊或請求 ID:

package main

import (
	"context"
	"fmt"
)

func main() {
	// 建立一個帶鍵值對的 context
	ctx := context.WithValue(context.Background(), "userID", 42)

	// 傳遞 context 到其他函式
	processRequest(ctx)
}

func processRequest(ctx context.Context) {
	// 從 context 中取出資料
	if userID := ctx.Value("userID"); userID != nil {
		fmt.Println("Processing request for user:", userID)
	} else {
		fmt.Println("No user ID found")
	}
}

執行邏輯:

  • 主函式設定了 userID,傳遞給 processRequest
  • 在子函式中,可以從 context 中提取出該資料,實現資訊的跨 API 共享。

context 的關鍵功能

  1. 取消訊號
    使用 context.WithCancel 來手動通知任務停止:

    ctx, cancel := context.WithCancel(context.Background())
    go func() {
        time.Sleep(1 * time.Second)
        cancel() // 通知任務取消
    }()
    
    <-ctx.Done() // 阻塞,直到取消訊號被觸發
    fmt.Println("Task cancelled")
    
  2. 超時控制
    使用 context.WithTimeout 自動取消任務:

    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    
    select {
    case <-time.After(3 * time.Second):
        fmt.Println("Task completed")
    case <-ctx.Done():
        fmt.Println("Timeout:", ctx.Err()) // 輸出超時錯誤
    }
    
  3. 資料傳遞
    使用 context.WithValue 跨 API 傳遞資料。


為什麼需要 context

  1. 解決併發中的控制問題
    在 Go 中,任務往往是透過 goroutine 併發執行的,但如何優雅地終止任務?傳統方法可能需要複雜的 channel 通訊,而 context 提供了一個統一的介面。

  2. 統一超時和取消機制
    無需手動編寫超時邏輯或全域性變數,context 內建了這些功能。

  3. 跨 API 共享資料
    context 可以像字典一樣儲存鍵值對,讓函式間更方便地傳遞資訊。


常用場景

  1. HTTP 請求處理:
    控制請求的超時、取消和資料傳遞。

  2. 資料庫操作:
    遇到慢查詢時,設定超時或取消。

  3. 微服務通訊:
    在分散式系統中傳遞請求 ID 或認證資訊。

  4. 併發任務協調:
    例如多個 goroutine 需要根據同一個取消訊號停止。


總結

通俗來說,context 是 Go 語言中用來管理任務生命週期傳遞資訊的工具,它讓我們可以更方便地實現超時控制、手動取消和跨 API 資料共享,簡化了併發程式設計中的複雜操作。