コンテキスト詳細
コンテキスト詳細
Section titled “コンテキスト詳細”Goのcontextパッケージは、リクエストスコープの値、キャンセレーション、タイムアウトを管理するための標準的な方法を提供します。
なぜコンテキストが必要なのか
Section titled “なぜコンテキストが必要なのか”ゴルーチンの課題
Section titled “ゴルーチンの課題”問題のあるコード:
func processRequest() { go longRunningTask() // ゴルーチンを起動 // 問題: ゴルーチンをキャンセルできない // 問題: タイムアウトを設定できない // 問題: リクエストスコープの値を渡せない}コンテキストの解決:
func processRequest(ctx context.Context) { go longRunningTask(ctx) // コンテキストを渡す // メリット: // - ゴルーチンをキャンセルできる // - タイムアウトを設定できる // - リクエストスコープの値を渡せる}メリット:
- キャンセレーション: ゴルーチンやリクエストをキャンセル可能
- タイムアウト: タイムアウトを設定可能
- 値の伝播: リクエストスコープの値を伝播可能
コンテキストの作成
Section titled “コンテキストの作成”基本コンテキスト
Section titled “基本コンテキスト”// 空のコンテキストctx := context.Background()
// TODOマーカー付きコンテキスト(開発中)ctx := context.TODO()タイムアウト付きコンテキスト
Section titled “タイムアウト付きコンテキスト”// 5秒後にタイムアウトctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)defer cancel()
// 指定時刻にタイムアウトdeadline := time.Now().Add(5 * time.Second)ctx, cancel := context.WithDeadline(context.Background(), deadline)defer cancel()キャンセル可能なコンテキスト
Section titled “キャンセル可能なコンテキスト”ctx, cancel := context.WithCancel(context.Background())defer cancel()
// 手動でキャンセルcancel()値付きコンテキスト
Section titled “値付きコンテキスト”type userIDKey struct{}
ctx := context.WithValue(context.Background(), userIDKey{}, "user-123")
// 値の取得userID := ctx.Value(userIDKey{}).(string)コンテキストの使用
Section titled “コンテキストの使用”タイムアウトの実装
Section titled “タイムアウトの実装”func fetchData(ctx context.Context) (string, error) { // コンテキストがキャンセルされたかチェック select { case <-ctx.Done(): return "", ctx.Err() case result := <-doWork(): return result, nil }}
func doWork() <-chan string { ch := make(chan string) go func() { time.Sleep(2 * time.Second) ch <- "result" }() return ch}
// 使用例ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)defer cancel()
result, err := fetchData(ctx)if err != nil { log.Printf("Error: %v", err) // context deadline exceeded}キャンセレーションの実装
Section titled “キャンセレーションの実装”func processItems(ctx context.Context, items []string) error { for _, item := range items { select { case <-ctx.Done(): return ctx.Err() // キャンセルされた default: // アイテムを処理 if err := processItem(ctx, item); err != nil { return err } } } return nil}
// 使用例ctx, cancel := context.WithCancel(context.Background())defer cancel()
go func() { time.Sleep(2 * time.Second) cancel() // 2秒後にキャンセル}()
err := processItems(ctx, items)if err != nil { log.Printf("Cancelled: %v", err)}実践的な例: HTTPリクエスト
Section titled “実践的な例: HTTPリクエスト”func makeRequest(ctx context.Context, url string) (*http.Response, error) { req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return nil, err }
client := &http.Client{} return client.Do(req)}
// 使用例ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)defer cancel()
resp, err := makeRequest(ctx, "https://api.example.com/data")if err != nil { if err == context.DeadlineExceeded { log.Println("Request timeout") } return err}defer resp.Body.Close()実践的な例: データベースクエリ
Section titled “実践的な例: データベースクエリ”func queryDatabase(ctx context.Context, query string) ([]Row, error) { rows, err := db.QueryContext(ctx, query) if err != nil { return nil, err } defer rows.Close()
var results []Row for rows.Next() { select { case <-ctx.Done(): return nil, ctx.Err() default: var row Row if err := rows.Scan(&row); err != nil { return nil, err } results = append(results, row) } } return results, nil}コンテキストの伝播
Section titled “コンテキストの伝播”func handler(ctx context.Context) { // リクエストIDを追加 requestID := generateRequestID() ctx = context.WithValue(ctx, "requestID", requestID)
// サブ関数にコンテキストを伝播 processRequest(ctx)}
func processRequest(ctx context.Context) { requestID := ctx.Value("requestID").(string) log.Printf("Processing request: %s", requestID)
// さらにサブ関数に伝播 processSubRequest(ctx)}Goのコンテキストのポイント:
- タイムアウト: WithTimeout/WithDeadlineによるタイムアウト設定
- キャンセレーション: WithCancelによるキャンセル
- 値の伝播: WithValueによるリクエストスコープの値の伝播
- エラーハンドリング: ctx.Err()によるエラー取得
コンテキストは、Goの並行処理において不可欠な機能です。適切に使用することで、リソースの適切な管理とキャンセレーションを実現できます。