Kotlin Fest 2025 - 内部実装から理解する Coroutines ― Continuation・Structured Concurrency・Dispatcher
by
kaseken (Kent Kaseda)
×
Copy
Open
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Slide 1
Slide 1 text
内部実装から理解するCoroutines Continuation‧Structured Concurrency‧Dispatcher Kotlin Fest 2025 @kentkaseda kaseken kaseken
Slide 2
Slide 2 text
はじめに スライド: https://speakerdeck.com/kaseken/kotlin-fest-2025-nei-bu-shi-zhuang-karali-jie-suru-coroutines-c ontinuationstructured-concurrencydispatcher 原稿: https://zenn.dev/kaseken/articles/681c5aea0639c7
Slide 3
Slide 3 text
本発表のゴール 1. Kotlin Coroutinesの中⼼的なコンセプトを “仕組みから” 理解していただく 2. Kotlin Coroutinesのソースコードを気軽に読めるようになっていただく
Slide 4
Slide 4 text
Kotlin Coroutinesの中⼼的なコンセプト 1. Continuation suspend関数は、どのように⼀時停⽌‧再開しているのか? 2. Dispatcher コルーチンは、どのように⾮同期処理‧並⾏処理を実⾏するのか? 3. Structured Concurrency Structured Concurrencyとは何か。そして、どのように実現されているのか?
Slide 5
Slide 5 text
1. Continuation suspend関数は、どのように⼀時停⽌‧再開しているのか? 2. Dispatcher コルーチンは、どのように⾮同期処理‧並⾏処理を実⾏するのか? 3. Structured Concurrency Structured Concurrencyとは何か。そして、どのように実現されているのか? Kotlin Coroutinesの中⼼的なコンセプト
Slide 6
Slide 6 text
suspend関数の仕様 「中断」と「再開」がどのように実現されているのかを内部実装から追う delayが呼ばれた後「中断」する delayが完了した後「再開」する https://pl.kotl.in/awpNrOZhv
Slide 7
Slide 7 text
Kotlin Coroutinesの内部実装の追い⽅ 1. JetBrains/kotlin1)のソースコードを読む 言語レベルのコア機能 (Continuation等) の仕組みを知りたいとき 2. Kotlin/kotlinx.coroutines2)のソースコードを読む 拡張ライブラリの機能 (Coroutine Builder・CoroutineScope・Dispatcher等) の仕組み を知りたいとき 3. コンパイル後のバイトコードをデコンパイルしたコードを読む コンパイラの仕組みを知りたいとき 1) https://github.com/JetBrains/kotlin 2) https://github.com/Kotlin/kotlinx.coroutines
Slide 8
Slide 8 text
suspend関数の内部実装の追い⽅ 1. JetBrains/kotlin1)のソースコードを読む 言語レベルのコア機能 (Continuation等) の仕組みを知りたいとき 2. Kotlin/kotlinx.coroutines2)のソースコードを読む 拡張ライブラリの機能 (Coroutine Builder・CoroutineScope・Dispatcher等) の仕組み を知りたいとき 3. コンパイル後のバイトコードをデコンパイルしたコードを読む コンパイラの仕組みを知りたいとき 1) https://github.com/JetBrains/kotlin 2) https://github.com/Kotlin/kotlinx.coroutines
Slide 9
Slide 9 text
suspend関数のバイトコードを表⽰ IntelliJ IDEAの「Tools > Kotlin > Show Kotlin Bytecode」でBytecodeを表⽰
Slide 10
Slide 10 text
「Decompile」を実⾏すると Javaコードが復元される suspend関数のバイトコードをデコンパイル
Slide 11
Slide 11 text
suspend関数のバイトコードをデコンパイル ① 関数定義 ② Continuationの初期化 ③ suspend関数の実⾏
Slide 12
Slide 12 text
セクション① 関数定義 suspend関数の引数に「Continuation」が追加されている suspend関数は、HelloWorldKtクラスのstatic関数として定義される コンパイラによるバイトコードへの変換 デコンパイルによるJavaコードの復元
Slide 13
Slide 13 text
Continuationとは何か? ● 各suspend関数は「Continuation」を持つ ● Continuationの「resumeWith」を呼ぶと、対応するsuspend関数が再開する https://github.com/JetBrains/kotlin/blob/0075ea767c181d7a3b3b5c123a0d15fcc278e447/libraries/stdlib/src/kotlin/coroutines/Continuation.kt#L16 Continuationとは「中断されたsuspend関数を再開するためのハンドラ」
Slide 14
Slide 14 text
Continuationの役割をサンプルコードで解説 main関数 mainのContinuation 覚えてほしいポイント ● 各suspend関数はContinuationを持つ ● ContinuationのresumeWithを呼ぶと 対応するsuspend関数が再開する
Slide 15
Slide 15 text
main関数 helloWorld関数 mainのContinuation mainのContinuationが引数として渡される helloWorldの Continuation 覚えてほしいポイント ● 各suspend関数はContinuationを持つ ● ContinuationのresumeWithを呼ぶと 対応するsuspend関数が再開する Continuationの役割をサンプルコードで解説
Slide 16
Slide 16 text
main関数 helloWorld関数 mainのContinuation helloWorldのContinuationが引数として渡される delay関数 helloWorldの Continuation 覚えてほしいポイント ● 各suspend関数はContinuationを持つ ● ContinuationのresumeWithを呼ぶと 対応するsuspend関数が再開する Continuationの役割をサンプルコードで解説
Slide 17
Slide 17 text
main関数 helloWorld関数 mainのContinuation delay関数 helloWorldの Continuation helloWorldのContinuationのresumeWithを呼ぶと helloWorld関数が再開する 覚えてほしいポイント ● 各suspend関数はContinuationを持つ ● ContinuationのresumeWithを呼ぶと 対応するsuspend関数が再開する Continuationの役割をサンプルコードで解説
Slide 18
Slide 18 text
main関数 helloWorld関数 mainのContinuation helloWorldの Continuation mainのContinuationのresumeWithを呼ぶと main関数が再開する 覚えてほしいポイント ● 各suspend関数はContinuationを持つ ● ContinuationのresumeWithを呼ぶと 対応するsuspend関数が再開する Continuationの役割をサンプルコードで解説
Slide 19
Slide 19 text
関数の引数に追加されたContinuationとは? 引数の「Continuation $completion」とは 呼び出し元の suspend関数に対応する Continuationである! …と言い切れれば単純だったが、実際にはこれは半分正解・半分不正解
Slide 20
Slide 20 text
セクション② suspend関数に紐づくContinuationの初期化
Slide 21
Slide 21 text
suspend関数の初回呼び出し時 呼び出し元のContinuationが渡される suspend関数に紐づくContinuationが初期化される
Slide 22
Slide 22 text
Continuationの初期化 invokeSuspendは再開時に呼ばれる すなわち、再開時にもHelloWorldKt.helloWorldが呼ばれる 再開時には、この関数のContinuationが渡される 「label」は、この関数がどこで再開すべきかを保持
Slide 23
Slide 23 text
invokeSuspendが再開時に呼ばれることの確認 https://github.com/JetBrains/kotlin/blob/0075ea767c181d7a3b3b5c123a0d15fcc278e447/libraries/stdlib/jvm/src/kotlin/coroutines/jvm/internal/ContinuationImpl.kt#L16 再開時に呼ばれるresumeWith内で invokeSuspendが呼ばれる 覚えてほしいポイント (再掲) ● 各suspend関数はContinuationを持つ ● ContinuationのresumeWithを呼ぶと 対応するsuspend関数が再開する
Slide 24
Slide 24 text
suspend関数の再開時 この関数のContinuationが渡される (初回実行時は “呼び出し元” のContinuationが渡される) Continuationの持つ「label」が取得される
Slide 25
Slide 25 text
セクション③ suspend関数の実⾏
Slide 26
Slide 26 text
セクション③ suspend関数の実⾏:初回実⾏時 元の関数 ここまで実⾏ labelが1に変更
Slide 27
Slide 27 text
セクション③ suspend関数の実⾏:再開時 元の関数 ここから実⾏
Slide 28
Slide 28 text
まとめ:Continuationによるsuspend関数の中断‧再開の仕組み helloWorld関数 mainの Continuation main関数
Slide 29
Slide 29 text
まとめ:Continuationによるsuspend関数の中断‧再開の仕組み helloWorld関数 mainの Continuation main関数 helloWorldの Continuationを初期化
Slide 30
Slide 30 text
まとめ:Continuationによるsuspend関数の中断‧再開の仕組み helloWorld関数 mainの Continuation main関数 helloWorldの Continuationを初期化 label: 0の範囲のコードを実⾏ labelを1に変更 delay関数 helloWorldの Continuation 中断
Slide 31
Slide 31 text
まとめ:Continuationによるsuspend関数の中断‧再開の仕組み helloWorld関数 mainの Continuation main関数 helloWorldの Continuationを初期化 label: 0の範囲のコードを実⾏ labelを1に変更 delay関数 helloWorldの Continuation 中断 🕰 1秒経過 helloWorldのContinuationの resumeWithが呼ばれる 再開
Slide 32
Slide 32 text
まとめ:Continuationによるsuspend関数の中断‧再開の仕組み helloWorld関数 mainの Continuation main関数 helloWorldの Continuationを初期化 label: 0の範囲のコードを実⾏ labelを1に変更 delay関数 helloWorldの Continuation 中断 🕰 1秒経過 再開 label: 1の範囲のコードを実⾏ helloWorldのContinuationの resumeWithが呼ばれる mainのContinuationの resumeWithが呼ばれる
Slide 33
Slide 33 text
Kotlin Coroutinesの中⼼的なコンセプト 1. Continuation suspend関数は、どうやって⼀時停⽌‧再開しているのか? 2. Dispatcher コルーチンは、どのように⾮同期処理‧並⾏処理を実⾏するのか? 3. Structured Concurrency Structured Concurrencyとは何か。そして、どのように実現されているのか?
Slide 34
Slide 34 text
Dispatcherの表⾯的な仕様 (Dispatchers.Default) ● Coroutine Builder (launch) に引数を指定しない場合、Dispatchers.Default (CPU-boundなタスク⽤) が利⽤され、バックグラウンドで⾮同期実⾏される ○ ID: 12, 13のバックグラウンドスレッドが使⽤されている https://pl.kotl.in/67I3RBx0v
Slide 35
Slide 35 text
Dispatcherの表⾯的な仕様 (Dispatchers.IO) https://pl.kotl.in/67I3RBx0v ● Coroutine Builder (launch) の引数に指定することで、Dispatchers.IO (IO-boundなタスク⽤) を利⽤できる ○ ID: 12, 13, 14, 15, 17のスレッドが使⽤されている
Slide 36
Slide 36 text
Dispatcherの内部実装の追い⽅ 1. JetBrains/kotlin1)のソースコードを読む 言語レベルのコア機能 (Continuation等) の仕組みを知りたいとき 2. Kotlin/kotlinx.coroutines2)のソースコードを読む 拡張ライブラリの機能 (Coroutine Builder・CoroutineScope・Dispatcher等) の仕組み を知りたいとき 3. コンパイル後のバイトコードをデコンパイルしたコードを読む コンパイラの仕組みを知りたいとき 1) https://github.com/JetBrains/kotlin 2) https://github.com/Kotlin/kotlinx.coroutines
Slide 37
Slide 37 text
launchのソースコードを読む https://github.com/Kotlin/kotlinx.coroutines/blob/8c27d51025d56a7b8de8eec2fb234b0094ce84f1/kotlinx-coroutines-core/common/src/Builders.common.kt#L44 Coroutine Builderの代表例として「launch」のソースコードを読む
Slide 38
Slide 38 text
launchのソースコードを読む https://github.com/Kotlin/kotlinx.coroutines/blob/8c27d51025d56a7b8de8eec2fb234b0094ce84f1/kotlinx-coroutines-core/common/src/Builders.common.kt#L44 コルーチンに紐づくCoroutineContext (= コルーチンに紐づくメタデータのコンテナ ) の初期化 Dispatcherも、CoroutineContextの一要素
Slide 39
Slide 39 text
launchのソースコードを読む https://github.com/Kotlin/kotlinx.coroutines/blob/8c27d51025d56a7b8de8eec2fb234b0094ce84f1/kotlinx-coroutines-core/common/src/Builders.common.kt#L44 コルーチン (StandaloneCoroutine) のインスタンスが初期化される
Slide 40
Slide 40 text
launchのソースコードを読む https://github.com/Kotlin/kotlinx.coroutines/blob/8c27d51025d56a7b8de8eec2fb234b0094ce84f1/kotlinx-coroutines-core/common/src/Builders.common.kt#L44 StandaloneCoroutine.startが呼ばれコルーチンが起動
Slide 41
Slide 41 text
AbstractCoroutine.startのソースコードを読む https://github.com/Kotlin/kotlinx.coroutines/blob/8c27d51025d56a7b8de8eec2fb234b0094ce84f1/kotlinx-coroutines-core/common/src/AbstractCoroutine.kt#L133 https://github.com/Kotlin/kotlinx.coroutines/blob/8c27d51025d56a7b8de8eec2fb234b0094ce84f1/kotlinx-coroutines-core/common/src/CoroutineStart.kt#L356 AbstractCoroutine.kt CoroutineStart.kt CoroutineStartのinvokeが呼ばれる startCoroutineCancellableが呼ばれる Cancellable.kt https://github.com/Kotlin/kotlinx.coroutines/blob/8c27d51025d56a7b8de8eec2fb234b0094ce84f1/kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt#L23
Slide 42
Slide 42 text
startCoroutineCancellableのソースコードを読む https://github.com/Kotlin/kotlinx.coroutines/blob/8c27d51025d56a7b8de8eec2fb234b0094ce84f1/kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt#L23
Slide 43
Slide 43 text
startCoroutineCancellableのソースコードを読む コルーチンに紐づく Continuation createCoroutineIntercepted https://github.com/Kotlin/kotlinx.coroutines/blob/8c27d51025d56a7b8de8eec2fb234b0094ce84f1/kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt#L23 コルーチンに紐づくContinuation が初期化される
Slide 44
Slide 44 text
startCoroutineCancellableのソースコードを読む コルーチンに紐づく Continuation createCoroutineIntercepted https://github.com/Kotlin/kotlinx.coroutines/blob/8c27d51025d56a7b8de8eec2fb234b0094ce84f1/kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt#L23 intercepted() コルーチンに紐づく Continuation DispatchedContinuation ContinuationがDispatchedContinuation でwrapされる
Slide 45
Slide 45 text
startCoroutineCancellableのソースコードを読む コルーチンに紐づく Continuation createCoroutineIntercepted https://github.com/Kotlin/kotlinx.coroutines/blob/8c27d51025d56a7b8de8eec2fb234b0094ce84f1/kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt#L23 intercepted() コルーチンに紐づく Continuation DispatchedContinuation resumeCancellableWith() Dispatcher DispatchedContinuationがdispatchされる
Slide 46
Slide 46 text
startCoroutineCancellableのソースコードを読む コルーチンに紐づく Continuation createCoroutineIntercepted intercepted() コルーチンに紐づく Continuation DispatchedContinuation resumeCancellableWith() Dispatcher DispatchedContinuationがdispatchされる wrapされたContinuationのresumeWithを呼ぶ (= コルーチンの実行 ) https://github.com/Kotlin/kotlinx.coroutines/blob/8c27d51025d56a7b8de8eec2fb234b0094ce84f1/kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt#L23
Slide 47
Slide 47 text
interceptedのソースコード https://github.com/JetBrains/kotlin/blob/15b95af041cb7068f1a721278f7a5c0057fbfa4e/libraries/stdlib/jvm/src/kotlin/coroutines/jvm/internal/ContinuationImpl.kt#L111 https://github.com/Kotlin/kotlinx.coroutines/blob/f4f519b36734238ec686dfaec1e174086691781e/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt#L240 CoroutineContextからCoroutineDispatcherが取り出され、interceptContinuationが呼ばれる ContinuationがDispatchedContinuationでwrapされる
Slide 48
Slide 48 text
resumeCancellableWithのソースコード https://github.com/Kotlin/kotlinx.coroutines/blob/8c27d51025d56a7b8de8eec2fb234b0094ce84f1/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt#L206C1-L219C6 CoroutineDispatcher.safeDispatchが呼ばれる CoroutineDispatcher.dispatchが呼ばれる https://github.com/Kotlin/kotlinx.coroutines/blob/8c27d51025d56a7b8de8eec2fb234b0094ce84f1/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt#L252
Slide 49
Slide 49 text
DispatchedTask.runのソースコード https://github.com/Kotlin/kotlinx.coroutines/blob/8c27d51025d56a7b8de8eec2fb234b0094ce84f1/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt#L77 DispatchedTaskのrunが呼ばれる (DispatchedContinuationの基底クラス) Continuation.resumeが呼ばれる (resume内でresumeWithが呼ばれる(ref))
Slide 50
Slide 50 text
Dispatcherの内部実装を追う コルーチンに紐づく Continuation createCoroutineIntercepted intercepted() コルーチンに紐づく Continuation DispatchedContinuation resumeCancellableWith() Dispatcher DispatchedContinuationがdispatchされる wrapされたContinuationのresumeWithを呼ぶ (= コルーチンの実行 ) launch
Slide 51
Slide 51 text
CoroutineDispatcherの種別 ● Dispatchers.Default CPU-Boundな処理の実⾏に使⽤ ● Dispatchers.IO IO-Boundな処理の実⾏に使⽤ (Dispatchers.Defaultと同じスレッドプールを利⽤) ● Dispatchers.Main メインスレッド (UIスレッド) で実⾏する CoroutineDispatcherの実装は、実⾏環境 (JVM/Native/Web) ごとに異なる Dispatchers.DefaultおよびDispatchers.IOのJVMにおける内部実装を調査
Slide 52
Slide 52 text
CoroutineDispatcher.dispatchが呼ばれた後の流れ Dispatchers.Default.dispatch (Ref) CoroutineScheduler.dispatch (Ref) Dispatchers.IO.dispatch (Ref) LimitedDispatchers.dispatch (Ref) SchedulerCoroutineDispatcher.dispatch (Ref) CoroutineScheduler.dispatchの実装は複雑なため、模式図を用いて解説する (⚠ここからの説明は簡略化したもので、実態とは異なります )
Slide 53
Slide 53 text
Dispatchers.Defaultでコルーチンが実⾏される仕組み CoroutineScheduler TaskQueue (CPU-bound) TaskQueue (IO-bound) Dispatchers.Defaultに コルーチンをdispatch CoroutineSchedulerのCPU-boundなタスク⽤のキューに追加される Thread Pool
Slide 54
Slide 54 text
CoroutineScheduler TaskQueue (CPU-bound) TaskQueue (IO-bound) Dispatchers.Defaultに コルーチンをdispatch タスクを処理するためのWorker (JavaのThreadの⼦クラス (Ref)) が作られる Worker (待機中) Dispatchers.Defaultでコルーチンが実⾏される仕組み Thread Pool
Slide 55
Slide 55 text
タスクの取得・実行 をループする CoroutineScheduler TaskQueue (CPU-bound) TaskQueue (IO-bound) Dispatchers.Defaultに コルーチンをdispatch Workerが起動され、タスクの取得‧コルーチン実⾏ (DispatchedTask.run) をループする Worker (タスク実行中) Dispatchers.Defaultでコルーチンが実⾏される仕組み Thread Pool
Slide 56
Slide 56 text
CoroutineScheduler TaskQueue (CPU-bound) TaskQueue (IO-bound) Dispatchers.Defaultに コルーチンをdispatch CPU-boundなタスクを処理中のWorkerの個数は、CPUのコア数以下に制限される (= 上限に達している場合には、dispatch時にWorkerの作成は⾏われない) Worker (タスク実行中) Worker (タスク実行中) Dispatchers.Defaultでコルーチンが実⾏される仕組み Thread Pool
Slide 57
Slide 57 text
Dispatchers.IOでコルーチンが実⾏される仕組み CoroutineScheduler TaskQueue (CPU-bound) TaskQueue (IO-bound) Dispatchers.IOに コルーチンをdispatch Worker (タスク実行中) Worker (タスク実行中) CoroutineSchedulerのIO-boundなタスク⽤のキューに追加される Thread Pool
Slide 58
Slide 58 text
CoroutineScheduler TaskQueue (CPU-bound) TaskQueue (IO-bound) Dispatchers.IOに コルーチンをdispatch Worker (タスク実行中) Worker (タスク実行中) Worker (待機中) Dispatchers.IOでコルーチンが実⾏される仕組み Thread Pool タスクを処理するためのWorker (JavaのThreadの⼦クラス (Ref)) が作られる
Slide 59
Slide 59 text
CoroutineScheduler TaskQueue (CPU-bound) TaskQueue (IO-bound) Dispatchers.IOに コルーチンをdispatch Worker (タスク実行中) Worker (タスク実行中) Worker (タスク実行中) タスクの取得・実行 をループする Dispatchers.IOでコルーチンが実⾏される仕組み Thread Pool Workerが起動され、タスクの取得‧コルーチン実⾏ (DispatchedTask.run) をループする (※Dispatchers.Defaultと同じスレッドプールを利⽤)
Slide 60
Slide 60 text
CoroutineScheduler TaskQueue (CPU-bound) TaskQueue (IO-bound) Dispatchers.IOに コルーチンをdispatch Worker (タスク実行中) Worker (タスク実行中) Worker (タスク実行中) Worker (タスク実行中) IO-boundなタスクを処理中のWorkerの個数には上限がない (※ ただし、実質的には上限がある。前段でlimitedParallelismによる制限がある) Worker (タスク実行中) Dispatchers.IOでコルーチンが実⾏される仕組み Thread Pool
Slide 61
Slide 61 text
IO-boundなタスクを処理中のWorkerの個数には上限がない (※ ただし、実質的には上限がある。前段でlimitedParallelismによる制限がある) CoroutineScheduler TaskQueue (CPU-bound) TaskQueue (IO-bound) Dispatchers.IOに コルーチンをdispatch Worker (タスク実行中) Worker (タスク実行中) Worker (タスク実行中) タスクの取得・実行 をループする Dispatchers.IOでコルーチンが実⾏される仕組み Thread Pool ⚠ ここまでの説明は簡略化したもので、実態とは異なります 実際には、Workerごとのローカルのタスクキューもあるなど、もっと複雑です 正確な実装を知りたい⽅は、CoroutineSchedulerのソースコード (Ref) を読んでください
Slide 62
Slide 62 text
limitedParallelismによる並列に実⾏されるコルーチン数の制限 limitedParallelism(2) limitedParallelism(10) https://pl.kotl.in/V1x5RYgiI
Slide 63
Slide 63 text
CoroutineDispatcher.dispatchが呼ばれた後の流れ (再掲) Dispatchers.Default.dispatch (Ref) CoroutineScheduler.dispatch (Ref) Dispatchers.IO.dispatch (Ref) LimitedDispatchers.dispatch (Ref) SchedulerCoroutineDispatcher.dispatch (Ref) CoroutineScheduler.dispatchの実装は複雑なため、模式図を用いて解説する (⚠ここからの説明は簡略化したもので、実態とは異なります ) limitedParallelismを指定すると LimitedDispatcherを介するようになる (Dispatchers.IOはデフォルトで.limitedParallelism(64)が適⽤
Slide 64
Slide 64 text
LimitedDispatcherの内部実装 CoroutineScheduler TaskQueue (CPU-bound) TaskQueue (IO-bound) Worker (タスク実行中) Worker (タスク実行中) Worker (タスク実行中) Worker (タスク実行中) Worker (タスク実行中) TaskQueue LimitedDispatcher Worker Worker Worker Dispatchers.IOに コルーチンをdispatch 前段のLimitedDispatcherで 実⾏中のコルーチン数が指定した並列数以下になるよう制限される 並列数を超える場合 このキューで待機 Workerの個数を 並列数以下に制御 Ref) https://github.com/Kotlin/kotlinx.coroutines/blob/8c27d51025d56a7b8de8eec2fb234b0094ce84f1/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt#L22
Slide 65
Slide 65 text
1. Continuation suspend関数は、どうやって⼀時停⽌‧再開しているのか? 2. Dispatcher コルーチンは、どのように⾮同期処理‧並⾏処理を実⾏するのか? 3. Structured Concurrency Structured Concurrencyとは何か。そして、どのように実現されているのか? Kotlin Coroutinesの中⼼的なコンセプト
Slide 66
Slide 66 text
Structured Concurrencyとは Parent Coroutine Child Coroutine ⼦コルーチンが完了するまで親コルーチンが完了しないこと
Slide 67
Slide 67 text
Structured Concurrencyとは Parent Coroutine Child Coroutine Structured Concurrencyに反する場合、リソースリークのリスクがある Kotlin Coroutinesでは (⼀般的な⽤法において) Structured Concurrencyが守られる ⚠リソースリークのリスク
Slide 68
Slide 68 text
Kotlin CoroutinesにおけるStructured Concurrency ● CoroutineScopeは、⼦コルーチンが全て完了してからスコープを抜ける ● 全てのコルーチンは、CoroutineScopeを起点に起動する必要がある ○ Coroutine Builder (launchやasync) はCoroutineScopeの拡張関数 Kotlin Coroutinesでは (⼀般的な⽤法において) Structured Concurrencyが守られる
Slide 69
Slide 69 text
CoroutineScopeの内部実装の追い⽅ 1. JetBrains/kotlin1)のソースコードを読む 言語レベルのコア機能 (Continuation等) の仕組みを知りたいとき 2. Kotlin/kotlinx.coroutines2)のソースコードを読む 拡張ライブラリの機能 (Coroutine Builder・CoroutineScope・Dispatcher等) の仕組み を知りたいとき 3. コンパイル後のバイトコードをデコンパイルしたコードを読む コンパイラの仕組みを知りたいとき 1) https://github.com/JetBrains/kotlin 2) https://github.com/Kotlin/kotlinx.coroutines
Slide 70
Slide 70 text
coroutineScopeのソースコード 呼び出し元のContinuationとCoroutineContextを使って CoroutineScopeに紐づくコルーチン「ScopeCoroutine」が初期化‧起動される
Slide 71
Slide 71 text
ScopeCoroutineのソースコード
Slide 72
Slide 72 text
ScopeCoroutineのソースコード AbstractCoroutine (基底クラス) の初期化時にコルーチンの親⼦構造が形成される
Slide 73
Slide 73 text
コルーチンの親⼦構造の形成 親のCoroutineContextに含まれる 親コルーチンのJobが initParentJobに渡される 親コルーチンのJobに対して ⼦コルーチンをattachChildすることで Jobの親⼦構造が形成
Slide 74
Slide 74 text
コルーチンの親⼦構造の形成 (イメージ図) mainのContinuation CoroutineContext Job coroutineScopeのコルーチン CoroutineContext Job attachChild launchのコルーチン CoroutineContext Job launchのコルーチン CoroutineContext Job CoroutineContextが 初期化時に渡される 親のCoroutineContextに含まれるJobに対してattachChildすることで コルーチン (Job) の親⼦構造が形成される attachChild
Slide 75
Slide 75 text
ScopeCoroutineのソースコード (再掲)
Slide 76
Slide 76 text
ScopeCoroutineのソースコード (再掲) afterCompletionかafterResumeが呼ばれると 呼び出し元のContinuationのresumeWithが呼ばれ、coroutineScopeを抜ける (呼ばれる条件が分かれば、CoroutineScopeが⼦コルーチンの完了を待つ仕組みが分かる!)
Slide 77
Slide 77 text
Jobの役割 Ref) https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/ New Active Completing Completed ⼦Jobの完了待ち Cancelling キャンセル/失敗 Cancelled ● コルーチンの状態管理 ● コルーチンの親⼦構造の管理 (および親⼦構造を通じた状態の伝播) Jobの状態遷移図 ⼦Jobのキャンセル待ち
Slide 78
Slide 78 text
Coroutine Scopeが⼦コルーチンの完了を待つ仕組み Ref) https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/JobSupport.kt Coroutine Context Job (ACTIVE) mainのContinuation
Slide 79
Slide 79 text
Coroutine Scopeが⼦コルーチンの完了を待つ仕組み Ref) https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/JobSupport.kt Coroutine Context Job (ACTIVE) mainのContinuation Coroutine Context Job (ACTIVE) ScopeCoroutine ScopeCoroutineが起動し 「mainのJob」と「ScopeCoroutineのJob」の間に親⼦構造が形成される
Slide 80
Slide 80 text
Coroutine Scopeが⼦コルーチンの完了を待つ仕組み Ref) https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/JobSupport.kt Coroutine Context Job (ACTIVE) mainのContinuation Coroutine Context ScopeCoroutine Coroutine Context Job (ACTIVE) Job (ACTIVE) StandaloneCoroutine #1 1つ⽬のlaunchによってStandaloneCoroutineが起動し 「ScopeCoroutineのJob」と「StandaloneCoroutine #1のJob」の間に親⼦構造が形成される
Slide 81
Slide 81 text
Coroutine Scopeが⼦コルーチンの完了を待つ仕組み Ref) https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/JobSupport.kt Coroutine Context Job (ACTIVE) mainのContinuation Coroutine Context ScopeCoroutine Coroutine Context Job (ACTIVE) Job (ACTIVE) StandaloneCoroutine #1 2つ⽬のlaunchによってStandaloneCoroutineが起動し 「ScopeCoroutineのJob」と「StandaloneCoroutine #2のJob」の間に親⼦構造が形成される Coroutine Context Job (ACTIVE) StandaloneCoroutine #2
Slide 82
Slide 82 text
Coroutine Scopeが⼦コルーチンの完了を待つ仕組み Ref) https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/JobSupport.kt Coroutine Context Job (ACTIVE) mainのContinuation Coroutine Context ScopeCoroutine Coroutine Context Job (ACTIVE) Job (COMPLETING) StandaloneCoroutine #1 `coroutineScope`内の処理が完了し ScopeCoroutineのJobがCOMPLETING (⼦コルーチンの完了待ち) になる Coroutine Context Job (ACTIVE) StandaloneCoroutine #2
Slide 83
Slide 83 text
AbstractCoroutine.resumeWithのソースコード coroutineScope内の処理の完了時に、ScopeCoroutineの`resumeWith`が呼ばれる 未完了の⼦コルーチンが残っている場合、returnして待機 ⼦コルーチンが全て完了している場合、`afterResume`を呼んでcoroutineScopeを抜ける https://github.com/Kotlin/kotlinx.coroutines/blob/8c27d51025d56a7b8de8eec2fb234b0094ce84f1/kotlinx-coroutines-core/common/src/AbstractCoroutine.kt#L98
Slide 84
Slide 84 text
Coroutine Scopeが⼦コルーチンの完了を待つ仕組み Ref) https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/JobSupport.kt Coroutine Context Job (ACTIVE) mainのContinuation Coroutine Context ScopeCoroutine Coroutine Context Job (ACTIVE) Job (COMPLETING) StandaloneCoroutine #1 `makeCompletingOnce`メソッド内で、いずれかの未完了の⼦コルーチンに対して 完了時に呼ばれるコールバック関数 (CompletionHandler) が登録される Coroutine Context Job (ACTIVE) StandaloneCoroutine #2 Completion Handler
Slide 85
Slide 85 text
Coroutine Scopeが⼦コルーチンの完了を待つ仕組み Ref) https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/JobSupport.kt Coroutine Context Job (ACTIVE) mainのContinuation Coroutine Context ScopeCoroutine Coroutine Context Job (ACTIVE) Job (COMPLETING) StandaloneCoroutine #1 StandaloneCoroutine #2が完了し、JobがCOMPLETEDになる Coroutine Context Job (COMPLETED) StandaloneCoroutine #2 Completion Handler
Slide 86
Slide 86 text
Coroutine Scopeが⼦コルーチンの完了を待つ仕組み Ref) https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/JobSupport.kt Coroutine Context Job (ACTIVE) mainのContinuation Coroutine Context ScopeCoroutine Coroutine Context Job (ACTIVE) Job (COMPLETING) StandaloneCoroutine #1 CompletionHandlerを介して、ScopeCoroutineのJobの`continueCompleting`が呼ばれる Coroutine Context Job (COMPLETED) StandaloneCoroutine #2 Completion Handler continueCompleting
Slide 87
Slide 87 text
continueCompletingのソースコード ※ 説明⽤に⼀部簡略化 https://github.com/Kotlin/kotlinx.coroutines/blob/8c27d51025d56a7b8de8eec2fb234b0094ce84f1/kotlinx-coroutines-core/common/src/JobSupport.kt#L965 未完了の⼦コルーチンが残っている場合 いずれかの未完了の⼦コルーチンにCompletionHandlerを登録してreturn 全ての⼦コルーチンが完了済みの場合 `afterCompletion`を呼んでcoroutineScopeを抜ける
Slide 88
Slide 88 text
Coroutine Scopeが⼦コルーチンの完了を待つ仕組み Ref) https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/JobSupport.kt Coroutine Context Job (ACTIVE) mainのContinuation Coroutine Context ScopeCoroutine Coroutine Context Job (ACTIVE) Job (COMPLETING) StandaloneCoroutine #1 Coroutine Context Job (COMPLETED) StandaloneCoroutine #2 Completion Handler `continueCompleting`メソッド内で、いずれかの未完了の⼦コルーチンに対して 完了時に呼ばれるコールバック関数 (CompletionHandler) が登録される
Slide 89
Slide 89 text
Coroutine Scopeが⼦コルーチンの完了を待つ仕組み Ref) https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/JobSupport.kt Coroutine Context Job (ACTIVE) mainのContinuation Coroutine Context ScopeCoroutine Coroutine Context Job (COMPLETED) Job (COMPLETING) StandaloneCoroutine #1 Coroutine Context Job (COMPLETED) StandaloneCoroutine #2 Completion Handler StandaloneCoroutine #1が完了し、JobがCOMPLETEDになる
Slide 90
Slide 90 text
Coroutine Scopeが⼦コルーチンの完了を待つ仕組み Ref) https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/JobSupport.kt Coroutine Context Job (ACTIVE) mainのContinuation Coroutine Context ScopeCoroutine Coroutine Context Job (COMPLETED) Job (COMPLETING) StandaloneCoroutine #1 Coroutine Context Job (COMPLETED) StandaloneCoroutine #2 Completion Handler CompletionHandlerを介して、ScopeCoroutineのJobの`continueCompleting`が呼ばれる continueCompleting
Slide 91
Slide 91 text
Coroutine Scopeが⼦コルーチンの完了を待つ仕組み Ref) https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/JobSupport.kt Coroutine Context Job (ACTIVE) mainのContinuation Coroutine Context ScopeCoroutine Coroutine Context Job (COMPLETED) Job (COMPLETED) StandaloneCoroutine #1 Coroutine Context Job (COMPLETED) StandaloneCoroutine #2 ⼦コルーチンが全て完了しているので、`afterCompleting`が呼ばれ、coroutineScopeを抜ける
Slide 92
Slide 92 text
Coroutine Scopeが⼦コルーチンの完了を待つ流れ `coroutineScope`内の処理実⾏が完了 未完了の⼦コルーチンが存在するか? afterResumeが呼ばれる いずれかの未完了の⼦コルーチンに CompletionHandlerを登録 CompletionHandlerが登録された ⼦コルーチンが完了 呼び出し元のresumeWithが呼ばれ、`coroutineScope`から抜ける 未完了の⼦コルーチンが存在するか? afterCompletionが呼ばれる NO YES NO YES
Slide 93
Slide 93 text
本発表のまとめ 1. Continuationの内部実装 → suspend関数は、どうやって⼀時停⽌‧再開しているのか? 2. Coroutine BuilderとDispatcherの内部実装 → コルーチンは、どのように⾮同期処理‧並⾏処理を実⾏するのか? 3. Coroutine ScopeとJobの内部実装 → Structured Concurrencyとは何か。そして、どのように実現されているのか?