Coroutine とは?一時停止して再開する関数
coroutine は、途中で一時停止し、制御を呼び出し元に返し、後でローカルの状態を保ったまま中断したところから正確に再開できる関数です。
通常の関数は呼び出されると最後まで実行されます。coroutine は途中で中断し、制御を譲り、変数をそのままにして後で再開できます。この協調的なモデルは Go、Kotlin、Python のような言語における軽量な並行性の基礎で、1 つの thread が何千もの OS thread よりはるかに安価に何千もの coroutine をさばけるようにします。
coroutine が関数とどう違うか
通常の関数には 1 つの入口と 1 つの出口があります。coroutine には複数の中断点があります。stack とローカルを保ったまま yield でき、後で再開できます。制御は OS の scheduler にプリエンプトされるのではなく、協調的に渡されます。
coroutine と thread
thread は OS によってプリエンプティブにスケジュールされ、比較的重量級です。coroutine はユーザー空間で協調的にスケジュールされ、極めて軽量なので、膨大な数を動かせます。トレードオフは、決して yield しない coroutine が他を starvation させうることです。
coroutine が可能にするもの
- 接続ごとに 1 つの goroutine のような、大量の並行性。
- きれいで逐次的なスタイルで書かれた非同期 I/O。
- 値を遅延的に一度に 1 つずつ生成する generator。
- OS thread のコストなしの協調的マルチタスク。
協調的スケジューリング
coroutine は自発的に yield するため、定められた地点(await や channel 操作など)でのみ切り替わります。これはプリエンプティブな thread よりも共有状態についての推論を容易にしますが、yield せずにブロックする coroutine はその scheduler 上のすべてを停止させえます。
簡単な例
Go では go handle(conn) を開始すると goroutine を起動します。これは runtime が OS thread の小さな pool に多重化する coroutine で、何千もの接続がごくわずかのコストで済みます。
CI における coroutine
coroutine ベースのテストコードは、coroutine が決して完了しないものを await するとハングし、suite を timeout まで停滞させえます。Latchkey は job の timeout を強制するため、詰まった coroutine が分を無期限に浪費せず、本当に一時的に見える失敗だけを retry します。
重要なポイント
- coroutine は中断して再開でき、一時停止をまたいでローカルの状態を保ちます。
- coroutine は軽量で協調的にスケジュールされ、プリエンプティブな OS thread とは異なります。
- 大量で安価な並行性を可能にしますが、決して譲らなければ scheduler を停止させえます。