Rubyの並列並行処理のこれまでとこれから
Ruby 1.8まで
M:1モデル(世間的には1:N)
1つのネイティブスレッドで複数(M個)の Rubyスレッドを管理するので、M:1 モデル
Rubyスレッドの切り替え
1. I/Oや sleep、Mutex などで待ちが入るタイミング 2. 一定時間(タイムスライス)が経過したときのタイミング
Ruby 1.9 Thread
1:1モデル
1つのネイティブスレッドに1つのRubyスレッド
GVLが導入され、Rubyスレッドは同時に1つしか動かないことが保証される 並行処理は行えるが並列処理は行えない
Ruby 1.9 Fiber
ユーザレベルスレッドの利点を残すために導入された
Ruby 1.8 のユーザレベルスレッドとほぼ同様ですが、タイムスライスがなく、I/O などで自動的にスイッチすることがない
Ruby 3.0 の Fiber scheduler
I/O 等のブロッキング処理をトリガーにフックを呼び出す仕組み Fiber scheduler
自分で Fiber をスケジュールする処理を記述するための仕組み
実際には、自分で記述するのではなく実装している gem を使うことが多いはず Ruby 3.0 の Ractor
1:1スレッド
Ractor内にRubyスレッドを1つ作り、1ネイティブスレッドを要求する
他言語の戦略のうち、Erlang, Elixirのアプローチを採用
領域を完全に分けてしまう: Unix などのプロセス、Racket(のPlace) Unixなどのプロセスに近いが、共有可能な部分は共有するしMutableなデータでもロックを必須とするなら共有可能にした
共有可能オブジェクトの制限が厳しく、ふつうのRubyプログラムはたいてい動かない
各Ractorが独自のVMロックを取得しているので、真の並列処理ができ、CPU負荷が高い処理でもマルチコア活用ができる
Ruby 3.3.0からM:Nスレッド導入
1:1モデルの欠点として、生成が遅いし生成できる数が少ない
RubyスレッドM個にたいしてネイティブスレッドがN個
ユーザーレベルスレッドで問題となっていたブロック問題
ブロックする可能性のある処理の実行中は1:1スレッドにしておく
準備のできたRubyスレッドを実行するためのネイティブスレッドを1つ余分に追加することで、他のRubyスレッドに処理がうつるようにする
M:Nスケジューラは、切り替わらなくなるかもしれない、といった危険のなくなった、そしてタイムスライスで切り替わる(プリエンプションがある)組み込みの Fiber schedulerみたいなもの、というとらえ方もできると思います。 よく似ているが違いもある
Rubyスレッドは同じRactorに所属していると同時に動かない(GVLをもつRubyスレッドしか動かせない) 3.3.0時点の挙動
RUBY_MN_THREADS=1 という環境変数でメインRactorでのM:Nスレッド対応を指定出来るようにする
Ractor性能改善
GCするために全てを止める必要があり、止めた状態で唯一のネイティブスレッド上でGCが実行されるようになっている
Ractor単位で並列にGCするRactor local GCを検討している