goroutineのキャンセル伝播について

published
2024-10-24
tags

初めに

pythonのasyncioでは、TaskGroupを用いて、実行タスクのCancelが可能なのですが、goroutineの場合、どのようにキャンセルを伝播すれば良いのか明確にわからなかった為、調べました。のメモ。

pythonのTaskGroupの実行タスクのCancelについては以下を参照ください。

本編

goroutineのサンプルコード編

まずは上記Python公式に記載のあるコードを例に、キャンセル伝播をしないverを、goroutineで記述したいと思います。

package main import ( "fmt" "sync" "time" ) func job(taskId int, sleepTime int) { fmt.Printf("Task %d: start\n", taskId) time.Sleep(time.Duration(sleepTime) * time.Second) fmt.Printf("Task %d: done\n", taskId) } func main() { var wg sync.WaitGroup for i := 1; i < 3; i++ { wg.Add(1) go func() { defer wg.Done() job(i, i) }() } wg.Wait() }

上記実行すると以下出力になりまする。

Task 2: start Task 1: start Task 1: done Task 2: done

2始まりな部分は正直疑問(1からだと思った...)なのですが、一旦置いておきます。終わりはsleepする時間差で1,2の順番になっていますね。

キャンセル編

goroutineのキャンセル伝播なのですが、調べてみると、goroutine間での値の共有や、goroutineの管理をするにはContextを用いるようです。

Contextは公式から

Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes

とあるように、デッドラインキャンセル信号リクエストスコープの値を伝播するようです。

また、公式DocにContextを用いる際の注意点も記載があるため、一読することをお勧めします。

では、Contextを用いてキャンセルの伝播を行うコードに変更していきます。
今回は1.5秒を目途にキャンセルを行います。そのためそれ以降の処理はdoneではなく、cancelと表示するようにします。

package main import ( "context" "fmt" "sync" "time" ) func job(ctx context.Context, taskId int, sleepTime int) { fmt.Printf("Task %d: start\n", taskId) select { case <-time.After(time.Duration(sleepTime) * time.Second): fmt.Printf("Task %d: done\n", taskId) case <-ctx.Done(): fmt.Printf("Task %d: cancel\n", taskId) } } func main() { var wg sync.WaitGroup ctx, cancel := context.WithCancel(context.Background()) defer cancel() for i := 1; i < 4; i++ { wg.Add(1) go func() { defer wg.Done() job(ctx, i, i) }() } time.Sleep(1500 * time.Millisecond) cancel() wg.Wait() }

キャンセルがわかりやすいように、goroutineの数を一つ増やしました。

実行した結果がこちら

Task 1: start Task 3: start Task 2: start Task 1: done Task 3: cancel Task 2: cancel

良いですね。キャンセル信号が正常に伝播され、1.5秒以降のタスクはCancel扱いとなっている為、意図した挙動になっています。

まとめ

今回はgoroutineに対してのキャンセル伝播はどのように行うのかを調べました。

Contextはキャンセル伝播だけでなく、デッドライン・リクエストスコープの値の伝播とまだまだ奥は深そうなので、時間があるときにもう少し遊んでみたいなぁというお気持ち。