Context

Go 語言 Context:取消信號、截止時間、請求範圍值。

Context 概念

Go Context

Context 用於在 API 邊界和進程間傳遞截止時間、取消信號和請求範圍值。

何時使用 Context?

  • Incoming requests(接收請求)
  • Outgoing requests(發送請求)

使用 Context 的標準庫

  • net
  • database/sql
  • os/exec

WithCancel

用於手動取消操作。

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    go worker(ctx, "worker-1")

    time.Sleep(3 * time.Second)
    cancel() // 發送取消信號
    time.Sleep(time.Second)
}

func worker(ctx context.Context, name string) {
    for {
        select {
        case <-ctx.Done():
            fmt.Printf("%s: received cancel signal\n", name)
            return
        default:
            fmt.Printf("%s: working...\n", name)
            time.Sleep(500 * time.Millisecond)
        }
    }
}

WithTimeout

在指定時間後自動取消。

func fetchData(ctx context.Context) error {
    ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
    defer cancel()

    select {
    case <-time.After(3 * time.Second): // 模擬長時間操作
        return nil
    case <-ctx.Done():
        return ctx.Err() // context deadline exceeded
    }
}

HTTP 請求超時

func fetchURL(url string) error {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return err // 超時會返回 context deadline exceeded
    }
    defer resp.Body.Close()
    return nil
}

WithDeadline

在指定時間點自動取消。

func main() {
    deadline := time.Now().Add(5 * time.Second)
    ctx, cancel := context.WithDeadline(context.Background(), deadline)
    defer cancel()

    select {
    case <-time.After(10 * time.Second):
        fmt.Println("operation completed")
    case <-ctx.Done():
        fmt.Println("deadline exceeded:", ctx.Err())
    }
}

WithValue

在請求間傳遞共享值,常用於傳遞請求 ID、認證資訊等。

type contextKey string

const (
    userIDKey    contextKey = "userID"
    requestIDKey contextKey = "requestID"
)

func main() {
    ctx := context.Background()
    ctx = context.WithValue(ctx, userIDKey, "user-123")
    ctx = context.WithValue(ctx, requestIDKey, "req-456")

    handleRequest(ctx)
}

func handleRequest(ctx context.Context) {
    userID := ctx.Value(userIDKey).(string)
    requestID := ctx.Value(requestIDKey).(string)
    fmt.Printf("User: %s, Request: %s\n", userID, requestID)
}
避免使用 WithValue 傳遞可選參數。Context value 應只用於跨 API 和進程邊界的請求範圍數據。

Context 傳播模式

鏈式傳遞

func main() {
    ctx := context.Background()
    ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
    defer cancel()

    if err := serviceA(ctx); err != nil {
        log.Fatal(err)
    }
}

func serviceA(ctx context.Context) error {
    // 可以添加自己的超時
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()

    return serviceB(ctx)
}

func serviceB(ctx context.Context) error {
    // 使用父 context 的截止時間
    select {
    case <-ctx.Done():
        return ctx.Err()
    default:
        // 處理業務邏輯
        return nil
    }
}

資料庫操作

func queryUser(ctx context.Context, db *sql.DB, id int) (*User, error) {
    ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
    defer cancel()

    row := db.QueryRowContext(ctx, "SELECT * FROM users WHERE id = ?", id)
    var user User
    if err := row.Scan(&user.ID, &user.Name); err != nil {
        return nil, err
    }
    return &user, nil
}

Links

Timeouts

Timeouts 對於連接外部資源或需要限制執行時間的程式很重要。

Timers

當你想在未來某個時間點執行一次操作時使用。
timer := time.NewTimer(2 * time.Second)
<-timer.C
fmt.Println("Timer fired")

// 可以取消
timer2 := time.NewTimer(time.Second)
go func() {
    <-timer2.C
    fmt.Println("Timer 2 fired")
}()
timer2.Stop() // 取消 timer

Tickers

當你想以固定間隔重複執行操作時使用。
ticker := time.NewTicker(500 * time.Millisecond)
done := make(chan bool)

go func() {
    for {
        select {
        case <-done:
            return
        case t := <-ticker.C:
            fmt.Println("Tick at", t)
        }
    }
}()

time.Sleep(2 * time.Second)
ticker.Stop()
done <- true

相關主題