本文へスキップ
BecomeCoder

Goコース · 第8章 実務のGo · レッスン31

context ― キャンセルとタイムアウトを伝える

ローカル実施

導入

サーバーがリクエストを処理している途中で、クライアントが接続を切ったり、処理に時間がかかりすぎたりしたら、どこかで処理を打ち切りたくなります。「この処理はもうやめてよい」という合図を、関数呼び出しの連鎖全体に伝える仕組みが 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つの目的で使われます。

  1. タイムアウト・締め切り:「このAPI呼び出しは3秒であきらめる」
  2. 手動キャンセル:ユーザーがブラウザの「戻る」を押した、上位の処理がエラーになった、などで下位の処理を止める

実務では、データベースへの問い合わせやHTTPクライアントなど、時間のかかる処理のほとんどが context.Context を受け取れるように作られています。「時間のかかる処理には context を渡す」が Go の共通言語だと考えておきましょう。