並行プログラミングを学ぶ一環で、「Contextを完全に理解する」というテーマでGo Conference 2021 Autumnに登壇しました の記事を見つけ、contextのcancel伝播の実装方法が気になった。そこで自分でcontextのcancel部分だけを自作することで伝播の理解を深めてみた。
実装はこちらで、context.WithCancel的なものとcontext.WithDeadline的なものを実装している。またテストコードも用意している。
実装してみて面白いなと思ったのは、contextの実装は可能な限りgoroutineを起動しないようにうまく実装されているということ。
自作する前にgolangのcontextの実装を眺めていたが、最初はこの辺りを見て、子の側でgoroutineを起動して親のDone()を見ているのだなと思い込んでいた。しかし実際には、
- 親となるcontextが全ての子を管理して親から子にcancelを伝えていくパターンである場合は、goroutineを起動せずに親に任せるというやり方を取っている。これにより無駄なgoroutineを起動する必要がない
- この辺りでparentCancelCtxを見たり、afterFuncerかを見たりして、早期returnすることでgoroutineを起動しないようになっている
- 親がそのようなパターンではない場合は、goroutineを起動して待つようにする。これによって独自にContext interfaceを実装しているケースでもうまくcancelを伝播させることができる
ということを知った。
なるほどと思ったので、cancel部分の自作バージョンでも以下のようにgoroutineを起動しないような仕組みになるように実装してみた。
- myCancelCtxのcancelは、保持している全ての子をcancelする
- myCancelCtxを生成するときに、親がmyCancelCtxを使っているケースなら、親のchildrenに入れるだけで何もしない
- そうでない場合だけgoroutineで親のDoneを待つ
標準のcontextはよく出来ていると実感した。また理解するには自作してみるのが一番深掘りできて良い。