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とは何か。そして、どのように実現されているのか?