Go言語におけるcontextパッケージの活用法

概要

Go言語のcontextパッケージは、複数のゴルーチン間でデータを伝達し、実行を制御するための重要な機能を提供します。本記事では、contextパッケージの基本的な使用方法と実践的な例を紹介します。

背景

次のような状況を考えてみましょう。

あなたはプロジェクトマネージャーです。チームメンバーにタスクを割り当て、進捗を監視しています。
ある日、重要なクライアント向けのレポート作成を依頼しました。チームメンバーは作業を開始しましたが、クライアントから急遽要件が変更になったとの連絡が入りました。
しかし、チームメンバーは既に作業に多くの時間を費やしています。どのようにして作業の中止を効率的に伝え、リソースを節約できるでしょうか?

ソフトウェア開発においても同様の問題が頻繁に発生します。例えば、ユーザーからのリクエストが途中でキャンセルされた場合、処理を継続することはリソースの無駄遣いとなります。このような状況で、contextパッケージが有効な解決策を提供します。

contextパッケージの基本

contextパッケージは、ゴルーチン間のデータ伝達と実行制御を可能にします。上記のシナリオでは、contextを使用してタスクのキャンセルを効率的に伝達できます。リクエストの開始時にcontextオブジェクトを作成し、関連するサービスに伝播させることで、必要に応じて処理のキャンセルを通知できます。

contextの基本的な使用方法

以下に、キャンセル機能を持つcontextの基本的な実装例を示します。

package main

import (
    "context"
    "fmt"
    "time"
)

func executeTask(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            if err := ctx.Err(); err != nil {
                fmt.Printf("エラー: %s\n", err)
            }
            fmt.Println("タスク終了:", time.Now().Format("15:04:05"))
            return
        case <-time.After(3 * time.Second):
            fmt.Println("タスク実行中:", time.Now().Format("15:04:05"))
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    go executeTask(ctx)
    time.Sleep(8 * time.Second)
    cancel()
    // ゴルーチンが終了メッセージを出力するのを待つ
    time.Sleep(1 * time.Second)
}

実行結果:

タスク実行中: 14:25:30
タスク実行中: 14:25:33
エラー: context canceled
タスク終了: 14:25:36

次に、タイムアウト機能を持つcontextの実装例を示します。

package main

import (
    "context"
    "fmt"
    "time"
)

func executeTask(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            if err := ctx.Err(); err != nil {
                fmt.Printf("エラー: %s\n", err)
            }
            fmt.Println("タスク終了:", time.Now().Format("15:04:05"))
            return
        case <-time.After(3 * time.Second):
            fmt.Println("タスク実行中:", time.Now().Format("15:04:05"))
        }
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
    defer cancel()
    go executeTask(ctx)
    time.Sleep(15 * time.Second)
}

実行結果:

タスク実行中: 14:32:15
タスク実行中: 14:32:18
エラー: context deadline exceeded
タスク終了: 14:32:21

ベストプラクティス

contextを使用する際のいくつかの重要なポイント:

  • contextが必要な関数では、最初のパラメータとしてcontextを受け取るのが慣習です。
  • contextが必要だが利用可能でない場合は、context.Background()を使用します。
  • 既にcontextを受け取っている関数から別の関数を呼び出す場合は、新しいcontextを作成せずに受け取ったcontextをそのまま渡します。
  • データを伝達する必要がある場合は、context.WithValue()を使用しますが、伝達されるデータは読み取り専用として扱い、同名のキーでは最も外側の値が有効になります。

タグ: Go contextパッケージ goroutine 並行処理 キャンセル処理

8月16日 08:22 投稿