crystal.tokyo #7 での発表資料です。 https://crystal.connpass.com/event/93629/
Concurrency2018.07.18 @at_grandpaCrystal.tokyo #7 in 渋谷
View Slide
@at_grandpa
圧倒亭グランパのブログ
Concurrency
✔ Concurrency の雰囲気を話します✔ 細かい syntax などはドキュメント参照
“as in Go or Clojure”✔ Go の goroutine/channel とほぼ同じ終了!
Concurrency の中身をちょっと覗いてみましょう
サンプルコード✔ 並行処理✔ 各処理からの値の取得
String型のChannelをインスタンス化
2つのFiberを生成
Channel経由で値を取得
2018-07-18 08:12:50 +09:00 start2018-07-18 08:12:50 +09:00 [fiber 1] start2018-07-18 08:12:50 +09:00 [fiber 2] start2018-07-18 08:12:55 +09:00 [top level] value: send from fiber 12018-07-18 08:12:55 +09:00 [fiber 1] end2018-07-18 08:13:00 +09:00 [top level] value: send from fiber 22018-07-18 08:13:00 +09:00 end
2018-07-18 08:12:50 +09:00 start2018-07-18 08:12:50 +09:00 [fiber 1] start2018-07-18 08:12:50 +09:00 [fiber 2] start2018-07-18 08:12:55 +09:00 [top level] value: send from fiber 12018-07-18 08:12:55 +09:00 [fiber 1] end2018-07-18 08:13:00 +09:00 [top level] value: send from fiber 22018-07-18 08:13:00 +09:00 end・並行に動いている・値も取れている
どういう仕組みで動いているか
✔ Fiber ✔ Runtime Scheduler ✔ Event Loop✔ Channel ✔ IO::SyscallConcurrencyを理解するポイント
Fiber✔ Process ⊃ Thread ⊃ Fiber ✔ 協調マルチタスク - Fiber自ら、処理を他のFiberに委譲する - 1つのFiberが固まるとシステム全体が固まる ✔ Crystalの処理は全てFiberで行われている - 「Main Fiber」でメインの処理を実行している
Runtime Scheduler✔ Fiberの切り替えを担当 ✔ クラス変数にFiberのqueueを持っている - @@runnables = Deque(Fiber).new - 実行可能Fiberのqueue✔ Scheduler.rescheduleで切り替え
Event Loop✔ I/O処理の委譲先 ✔ 委譲している間に別のFiberを実行できる✔ I/O処理が終了したら委譲元のFiberに移る
Channel✔ Fiber間のデータのやりとり ✔ 送信元Fiberや受信先Fiberを保持 - @senders = Deque(Fiber).new - @receivers = Deque(Fiber).new✔ 送受信時にFiberを切り替え - Runtime Scheduler を使う
IO::Syscall✔ 以下でincludeされている - Crystal::System::FileDescriptor - Socket ✔ read/writeでFiber切り替え - Runtime Scheduler を使うほぼ全てのI/Oを網羅
実際の動き追う
Channelをインスタンス化
・Fiber1を定義・Runtime Scheduler の 実行可能Fiberのqueueに Fiber1が enqueue される
concurrency.cr・Fiber1を定義・Runtime Scheduler の 実行可能Fiberのqueueに Fiber1が enqueue される
・Fiber2を定義・Runtime Scheduler の 実行可能Fiberのqueueに Fiber2が enqueue される
・実際の処理はここから開始・Top Level のコードは 「Main Fiber」で動いている・Channelの送受信時には Fiberの切り替えが行われる
・実際の処理はここから開始・Top Level のコードは 「Main Fiber」で動いている・Channelの送受信時には Fiberの切り替えが行われる・Runtime Scheduler の 実行可能Fiberのqueue からshift・Fiber1に処理が移る
・Fiber1に処理が移った
・sleep
concurrency.cr
concurrency.crfiber.cr
concurrency.crfiber.cr・Event Loop に処理を委譲・Runtime Scheduler で queueの次のFiber切り替え
concurrency.crfiber.cr・Event Loop に処理を委譲・Runtime Scheduler で queueの次のFiber切り替え・Fiber2へ移る
・Fiber2に処理が移った
・同じくFiber切り替え・しかし、もう 実行可能Fiberのqueueには Fiberが存在しない・I/O処理を待機するしかない
待機中 . . .
・Event Loop が sleep 5 の 終了を検知・処理中のFiberが他にいないので Fiber1の処理が再開される
・Channelに値を送信・sendの場合はreceiveを 呼んだFiberに切り替え・Main Fiber に切り替わる・このとき、sendしたFiberを Schedulerのqueueにenqueue
・受信された値を表示
・Schedulerの 実行可能Fiberのqueueを元に Fiberの切り替え・queueにはsendした際に enqueueされたFiber1が入っている
・Schedulerの 実行可能Fiberのqueueを元に Fiberの切り替え・queueにはsendした際に enqueueされたFiber1が入っている・Fiber1が再開
・値の表示
・Fiber1のブロックが終了・Fiberの切り替えが発生・しかし、Schedulerの queueには実行可能Fiberが 存在しない
・Event Loop が sleep 10 の 終了を検知・処理中のFiberが他にいないので Fiber2に処理が戻る
・そのまま終了
・そのまま終了・ここは通らない
・そのまま終了・ここは通らない2018-07-18 08:12:50 +09:00 start2018-07-18 08:12:50 +09:00 [fiber 1] start2018-07-18 08:12:50 +09:00 [fiber 2] start2018-07-18 08:12:55 +09:00 [top level] value: send from fiber 12018-07-18 08:12:55 +09:00 [fiber 1] end2018-07-18 08:13:00 +09:00 [top level] value: send from fiber 22018-07-18 08:13:00 +09:00 end [fiber 2] end は表示されてない
複雑!
とはいえ✔ Concurrencyに必要な役者を知る - Fiber, Runtime Scheduler, Event loop, Channel ✔ Fiberが切り替わるタイミングを知る - I/Oの場合 - 実行可能Fiber-queueからshift - receiveの場合 - 実行可能Fiber-queueからshift - sendしたFiberをqueueにenqueue - sendの場合 - receiveしたFiberに切り替えこれらを知るだけで、だいぶ変わる
まとめ
✔ 並行処理✔ Concurrencyに必要な役者を知ろう ✔ Fiberを切り替えるタイミングを知ろうConcurrency
✔ 立て続けにsendされたらどうなるの? - sendされた値もqueueに保存される - receiveを呼ぶ度にqueueからshift✔ Fiber内でI/O以外の重い処理があったら? - 委譲できない処理はそのまま処理される - その処理が終わるまで他のFiberは実行できない ✔ 入れ子spawnとかどうなるんだろう? - \(^o^)/まだまだあるよ
Happy Crystalling !fin