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

Timeout は外部がいぶリソースへの接続せつぞく実行じっこう時間じかん制限せいげんする必要ひつようがあるプログラムに重要じゅうよう

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

関連トピック