Structured concurrency – Roman Elizarov – Mediumでは、Kotlin coroutine 0.26.0でCoroutineScopeを導入した経緯などが書かれているんですが、タイトルのStructured concurrencyってのが結局なんなのか本文中にはあんまり出てこないんですよね。
んで、CoroutineScopeの動作の理解に精一杯で、coroutineScope
関数がなんなのか、どういう時に使うべきなのかよくわからないし、ましてやsupervisorScope
関数なんて言われた日には意味が不明なわけです。
とりあえずlaunch
関数内でasync
関数呼び出したら駄目なんだな〜ふ〜んなんで?
Notes on structured concurrency, or: Go statement considered harmfulを頑張って読む
Structured concurrency – Roman Elizarov – MediumのFurther readingにNotes on structured concurrency, or: Go statement considered harmful — njs blogを読むことを強く勧める!と書いています。パッと開くと超長いので心が折れかけるんですが読んでみたら面白かったのでメモします。
並行性の構造化
Structured concurrencyは概ね 並行性の構造化
って訳すとよさそうです。
構造化プログラミングはgotoを駆逐するために考えられたものですが、並行性の構造化
は並行処理におけるgoto的なものを駆逐するための議論みたいです。
構造化プログラミングは順次
, 反復
, 分岐
の構造を組み合わせてプログラム書くことでgotoは無くせるしむしろいい感じになるぜって話ですね。
並行性の中のgoto
エントリではgo statement(goroutineの起動)はgotoみたいなもんじゃ!って言ってます。go statementに限らず並行処理を起動するものはどれも同じみたいです。 たとえばtry-with-resources(これはJava)の中でスレッドを起動してその中でリソースにアクセスしたらどうなるか?ぶっ壊れます。 gotoじゃねえか!ということだそうです。でも確かにわかる気もする。
Trioの例
https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/#nurseries-a-structured-replacement-for-go-statements では、並行性についても構造化プログラミングと同じように並行性の構造のモデルを定義して取り入れるといいんじゃないか的な話と実際にpythonのtrioというライブラリの說明が始まります。
とりあえず図としてはこれがわかりやすいんじゃないかと思います。ほかのパターンもあるのかはちょっと分からないですがCoroutineScopeを理解するには十分かなと思います。
fork/joinに似てますけど実際に影響を受けてるらしいです。つまりどっから並行性が始まってどこで終わるんや、ってことを明示的に書けってことですね。これCoroutineScopeですね。こういう感じ。
suspend fun startParallel() = coroutineScope { // 並行性の分岐の根本 // 3並行に分かれる val a = async { requestA() } val b = async { requestB() } val c = async { requestC() } // 合流する a.await() + b.await() + c.await() }
タイミングがずれるケースとか複雑に分岐するケースとかもあるとおもうんですが、fork/joinの根本と合流がcoroutineScopeで適切に切られていればOKってことですね〜
例外の伝搬とCoroutineScope
例外の伝搬についてもfork/joinの図がわかりやすいですね。分岐の何処かが死んだら全体が即座に死ぬ。並行実行の単位がスコープってことですかね〜。
launch
関数内でスコープを切らずにasync
関数呼び出したら駄目なのは、async
関数が並行性の分岐を表すからで、スコープを切らない場合はforkの根本がlaunch関数の親のスコープになるから、と說明できそうです(図を書いてみたもののカチッとしなかったので省略)。
val job = Job() val scope = CoroutineScope(Dispatchers.Main + job) // asyncがエラーになるとscopeがキャンセルされる scope.launch { val a = async { requestA() }.await() }
終わりに
とりあえず雑ですがメモです。大分わかってきました。まだ理解できてないとこもありそうですが.. ざっくりしか読んでないからまた何度か読みます。 Notes on structured concurrency, or: Go statement considered harmful — njs blog