概要
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()を使用しますが、伝達されるデータは読み取り専用として扱い、同名のキーでは最も外側の値が有効になります。