グリーンスレッドを使用したルビーの「協調的」スレッドについて知っています。処理に複数の CPU コアを利用するために、アプリケーションで実際の「OS レベル」のスレッドを作成するにはどうすればよいですか?
9 に答える
Jörg の 2011 年 9 月のコメントで更新
ここでは、Ruby プログラミング言語と、Ruby プログラミング言語の特定の実装の特定のスレッド モデルという2 つの非常に異なるものを混同しているようです。現在、Ruby プログラミング言語には約 11 の異なる実装があり、非常に異なる独自のスレッド モデルを備えています。
(残念ながら、これらの 11 の実装のうち実際に本番環境で使用できるのは 2 つだけですが、年末までにはその数はおそらく 4 つまたは 5 つに増えるでしょう。) (更新: 現在は 5 つです: MRI、JRuby、YARV (インタープリター) Ruby 1.9 用)、Rubinius および IronRuby)。
最初の実装には実際には名前がありません。そのため、参照するのが非常に厄介であり、本当に煩わしく、混乱を招きます。Ruby プログラミング言語の機能と特定の Ruby 実装との間で際限のない混乱を招くため、名前がないよりもさらに厄介で混乱を招くのは、ほとんどの場合「Ruby」と呼ばれます。
「MRI」(「Matz の Ruby 実装」)、CRuby、または MatzRuby と呼ばれることもあります。
MRI は Ruby Threads をそのインタープリター内の Green Threads として実装します。残念ながら、これらのスレッドを並行してスケジュールすることはできません。一度に 1 つのスレッドしか実行できません。
ただし、任意の数の C スレッド (POSIX スレッドなど) を Ruby スレッドと並行して実行できるため、外部 C ライブラリ、または独自のスレッドを作成する MRI C 拡張機能を並行して実行できます。
2 つ目の実装はYARV (「Yet Another Ruby VM」の略) です。YARV は Ruby スレッドを POSIX または Windows NT スレッドとして実装しますが、グローバル インタープリター ロック (GIL) を使用して、一度に 1 つの Ruby スレッドのみを実際にスケジュールできるようにします。
MRI と同様に、C スレッドは実際には Ruby スレッドと並行して実行できます。
将来的には、GILがより細かいロックに分割され、より多くのコードが実際に並行して実行できるようになる可能性がありますが、それはまだ先のことであり、まだ計画されていません。
JRuby は Ruby Threads を Native Threads として実装します。JVM の場合の「Native Threads」は明らかに「JVM Threads」を意味します。JRuby はそれらに追加のロックを課しません。したがって、これらのスレッドが実際に並行して実行できるかどうかは、JVM によって異なります。JVM スレッドを OS スレッドとして実装するものもあれば、グリーン スレッドとして実装するものもあります。(Sun/Oracle の主流の JVM は、JDK 1.3 以降、OS スレッドのみを使用します)
XRubyは、Ruby スレッドを JVM スレッドとして実装しています。更新: XRuby は終了しました。
IronRuby は Ruby Threads を Native Threads として実装します。ここで、CLR の場合の「Native Threads」は明らかに「CLR Threads」を意味します。IronRuby はそれらに追加のロックを課さないため、CLR がサポートしている限り、並列で実行する必要があります。
Ruby.NETはRuby Threads を CLR Threads としても実装します。更新: Ruby.NET は終了しました。
Rubinius は、仮想マシン内で Ruby スレッドをグリーン スレッドとして実装しています。より正確には: Rubinius VM は、「Task」と呼ばれる非常に軽量で非常に柔軟な同時実行/並列処理/非ローカル制御フロー コンストラクトと、他のすべてのコンカレンシー コンストラクト (このディスカッションのスレッドだけでなく、Continuations、Actorsなども含む) をエクスポートします。 ) は、タスクを使用して純粋な Ruby で実装されます。
Rubinius は (現在) スレッドを並行してスケジュールすることはできませんが、それはそれほど大きな問題ではありません。Rubinius はすでに、1 つの Rubinius プロセス内で複数の POSIX スレッドで複数の VM インスタンスを並行して実行できます。スレッドは実際には Ruby で実装されているため、他の Ruby オブジェクトと同様に、シリアル化して別の POSIX スレッド内の別の VM に送信できます。(これは、BEAM Erlang VM が SMP 同時実行に使用するモデルと同じです。これは、すでにRubinius Actors に実装されています。)
更新: この回答の Rubinius に関する情報は、もう存在しない Shotgun VM に関するものです。「新しい」C++ VM は、複数の VM にわたってスケジュールされたグリーン スレッド (つまり、Erlang/BEAM スタイル) を使用せず、複数のネイティブ OS スレッド モデルを備えた、より伝統的な単一の VM を使用します。 、およびほぼすべての JVM です。
MacRubyは、Objective-C ランタイムと CoreFoundation および Cocoa フレームワークの上にある YARV のポートとして始まりました。現在は YARV とは大きく異なりますが、現在でも同じスレッド モデルを YARV と共有しています。 更新: MacRuby は、非推奨と宣言されている Apple ガベージ コレクターに依存しており、MacOSX の以降のバージョンでは削除される予定です。MacRuby はアンデッドです。
Cardinalは、 Parrot Virtual Machineの Ruby 実装です。スレッドはまだ実装されていませんが、実装された場合はおそらくParrot Threadsとして実装されるでしょう。更新: カーディナルは非常に不活発/死んでいるようです。
MagLevは、 GemStone/S Smalltalk VMの Ruby 実装です。GemStone/S がどのスレッド モデルを使用しているか、MagLev がどのスレッド モデルを使用しているか、さらにはスレッドがまだ実装されているかどうか (おそらく実装されていない) についても、私は知りません。
HotRubyは、独自の完全な Ruby 実装ではありません。これは、JavaScript での YARV バイトコード VM の実装です。HotRuby はスレッドをサポートしていません (まだ?)。サポートされたとしても、JavaScript は真の並列処理をサポートしていないため、スレッドを並列で実行することはできません。ただし、HotRuby の ActionScript バージョンがあり、ActionScript は実際に並列処理をサポートしている可能性があります。更新: HotRuby は終了しました。
残念ながら、これら 11 の Ruby 実装のうち、実際に本番環境に対応しているのは MRI と JRuby の 2 つだけです。
したがって、真の並列スレッドが必要な場合は、JRuby が現在唯一の選択肢です。それが悪いというわけではありません。JRuby は実際には MRI よりも高速であり、間違いなくより安定しています。
それ以外の場合、「古典的な」Ruby ソリューションは、スレッドの代わりにプロセスを並列処理に使用することです。Ruby Core Library には、別の Ruby プロセスを非常に簡単にフォークできる
メソッドを備えたProcess
モジュールが含まれています。また、Ruby 標準ライブラリには
Distributed Ruby (dRuby / dRb)ライブラリが含まれており、Ruby コードを同じマシン上だけでなくネットワーク上でも複数のプロセスに簡単に分散できます。Process.fork
Ruby 1.8 にはグリーン スレッドしかありません。実際の「OS レベル」のスレッドを作成する方法はありません。しかし、Ruby 1.9 にはファイバーと呼ばれる新しい機能が追加され、実際の OS レベルのスレッドを作成できるようになります。残念ながら、Ruby 1.9 はまだベータ版であり、数か月後には安定する予定です。
もう 1 つの方法は、JRuby を使用することです。JRuby はスレッドを OS レベルのスレッドとして実装し、「グリーン スレッド」はありません。JRuby の最新バージョンは 1.1.4 で、Ruby 1.8 と同等です。
それは実装に依存します:
- MRIにはありませんが、YARVの方が近いです。
- JRuby と MacRuby は持っています。
Ruby には、および のようなクロージャーがあります。JRuby のクロージャーと複数のコアを最大限に活用するには、Java のエグゼキューターが便利です。MacRuby の場合、 GCD の queuesが好きです。
実際の「OS レベル」のスレッド
を作成できるからといって、並列処理に複数の CPU コアを使用できるわけではないことに注意してください。以下の例を見てください。Blocks
lambdas
Procs
これは、Ruby 2.1.0 を使用して 3 つのスレッドを使用する単純な Ruby プログラムの出力です。
(jalcazar@mac ~)$ ps -M 69877
USER PID TT %CPU STAT PRI STIME UTIME COMMAND
jalcazar 69877 s002 0.0 S 31T 0:00.01 0:00.04 /Users/jalcazar/.rvm/rubies/ruby-2.1.0/bin/ruby threads.rb
69877 0.0 S 31T 0:00.01 0:00.00
69877 33.4 S 31T 0:00.01 0:08.73
69877 43.1 S 31T 0:00.01 0:08.73
69877 22.8 R 31T 0:00.01 0:08.65
ここでわかるように、4 つの OS スレッドがありますが、状態を持つスレッドのみR
が実行されています。これは、Ruby のスレッドの実装方法の制限によるものです。
JRuby を使用した同じプログラム。state の 3 つのスレッドが表示R
されます。これは、それらが並行して実行されていることを意味します。
(jalcazar@mac ~)$ ps -M 72286
USER PID TT %CPU STAT PRI STIME UTIME COMMAND
jalcazar 72286 s002 0.0 S 31T 0:00.01 0:00.01 /Library/Java/JavaVirtualMachines/jdk1.7.0_25.jdk/Contents/Home/bin/java -Djdk.home= -Djruby.home=/Users/jalcazar/.rvm/rubies/jruby-1.7.10 -Djruby.script=jruby -Djruby.shell=/bin/sh -Djffi.boot.library.path=/Users/jalcazar/.rvm/rubies/jruby-1.7.10/lib/jni:/Users/jalcazar/.rvm/rubies/jruby-1.7.10/lib/jni/Darwin -Xss2048k -Dsun.java.command=org.jruby.Main -cp -Xbootclasspath/a:/Users/jalcazar/.rvm/rubies/jruby-1.7.10/lib/jruby.jar -Xmx1924M -XX:PermSize=992m -Dfile.encoding=UTF-8 org/jruby/Main threads.rb
72286 0.0 S 31T 0:00.00 0:00.00
72286 0.0 S 33T 0:00.00 0:00.00
72286 0.0 S 31T 0:00.09 0:02.34
72286 7.9 S 31T 0:00.15 0:04.63
72286 0.0 S 31T 0:00.00 0:00.00
72286 0.0 S 31T 0:00.00 0:00.00
72286 0.0 S 31T 0:00.00 0:00.00
72286 0.0 S 31T 0:00.04 0:01.68
72286 0.0 S 31T 0:00.03 0:01.54
72286 0.0 S 31T 0:00.00 0:00.00
72286 0.0 S 31T 0:00.01 0:00.01
72286 0.0 S 31T 0:00.00 0:00.01
72286 0.0 S 31T 0:00.00 0:00.03
72286 74.2 R 31T 0:09.21 0:37.73
72286 72.4 R 31T 0:09.24 0:37.71
72286 74.7 R 31T 0:09.24 0:37.80
同じプログラムが MacRuby に。また、並行して実行される 3 つのスレッドもあります。これは、MacRuby スレッドが POSIX スレッド(実際の「OS レベル」のスレッド) であり、GVL がないためです。
(jalcazar@mac ~)$ ps -M 38293
USER PID TT %CPU STAT PRI STIME UTIME COMMAND
jalcazar 38293 s002 0.0 R 0T 0:00.02 0:00.10 /Users/jalcazar/.rvm/rubies/macruby-0.12/usr/bin/macruby threads.rb
38293 0.0 S 33T 0:00.00 0:00.00
38293 100.0 R 31T 0:00.04 0:21.92
38293 100.0 R 31T 0:00.04 0:21.95
38293 100.0 R 31T 0:00.04 0:21.99
もう一度、同じプログラムですが、今は古き良き MRI を使用しています。この実装はグリーン スレッドを使用するため、スレッドは 1 つしか表示されません。
(jalcazar@mac ~)$ ps -M 70032
USER PID TT %CPU STAT PRI STIME UTIME COMMAND
jalcazar 70032 s002 100.0 R 31T 0:00.08 0:26.62 /Users/jalcazar/.rvm/rubies/ruby-1.8.7-p374/bin/ruby threads.rb
Ruby のマルチスレッド化に興味がある場合は、フォーク ハンドラーを使用した並列プログラムのデバッグというレポートが興味深いものになるかもしれません。
Ruby 内部のより一般的な概要については、Ruby Under a Microscopeをお読みください。
また、Ruby Threads and the Global Interpreter Lock in C in Omniref では、Ruby スレッドが並列で実行されない理由をソース コードで説明しています。
drbの使用はどうですか?これは実際のマルチスレッドではなく、複数のプロセス間の通信ですが、1.8 で使用できるようになり、かなり摩擦が少なくなりました。
Ruby で製品レベルのシステム (ベータ版を使用できない) で並列処理が本当に必要な場合は、おそらくプロセスの方が優れた代替手段です。
しかし、最初に JRuby の下でスレッドを試す価値は間違いなくあります。
また、Ruby での将来のスレッド化に興味がある場合は、この記事が役に立つかもしれません。
MRI を使用している場合は、拡張機能として、または ruby-inline gem を使用して、スレッド コードを C で記述できます。
これは、Linda の Ruby 実装 (並列処理と分散コンピューティングのパラダイム) である Rinda に関する情報です。