導入
サーバーがリクエストを処理している途中で、クライアントが接続を切ったり、処理に時間がかかりすぎたりしたら、どこかで処理を打ち切りたくなります。「この処理はもうやめてよい」という合図を、関数呼び出しの連鎖全体に伝える仕組みが context(コンテキスト) です。
説明
context.Context は、Goの関数間で「キャンセルの合図」や「締め切り時刻」、「リクエストに紐づく値」を運ぶための入れ物です。慣習として、関数の第一引数に ctx context.Context を渡します。
package main
import (
"context"
"fmt"
"time"
)
func work(ctx context.Context) {
select {
case <-time.After(3 * time.Second):
fmt.Println("処理が完了しました")
case <-ctx.Done():
fmt.Println("キャンセルされました:", ctx.Err())
}
}
func main() {
// 1秒でタイムアウトするコンテキストを作る
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel() // リソースの後始末は忘れずに
work(ctx)
}
context.Background()… すべてのcontextのおおもと。ここから派生させて使います。context.WithTimeout(親ctx, 期間)… 「指定した期間が過ぎたら自動でキャンセルされる」子のcontextを作ります。ctx.Done()… キャンセルされた(またはタイムアウトした)ときに閉じられるチャネル。selectで受信を待つのが定石です。defer cancel()…WithTimeoutが確保したリソースを、使い終わったら必ず解放します。
上の例では、本来の処理に3秒かかるのに対してタイムアウトを1秒に設定しているため、work は「処理が完了しました」ではなく「キャンセルされました」を表示して終わります。
context は主に次の2つの目的で使われます。
- タイムアウト・締め切り:「このAPI呼び出しは3秒であきらめる」
- 手動キャンセル:ユーザーがブラウザの「戻る」を押した、上位の処理がエラーになった、などで下位の処理を止める
実務では、データベースへの問い合わせやHTTPクライアントなど、時間のかかる処理のほとんどが context.Context を受け取れるように作られています。「時間のかかる処理には context を渡す」が Go の共通言語だと考えておきましょう。