回答:
Jörgの2011年9月のコメントで更新
ここでは、Rubyプログラミング言語と、Rubyプログラミング言語の1つの特定の実装の特定のスレッドモデルという2つの非常に異なるものが混乱しているようです。現在、Rubyプログラミング言語には約11の異なる実装があり、非常に異なる独自のスレッドモデルがあります。
(残念ながら、これらの11個の実装の2つだけでは、実際に生産使用のために準備ができているが、今年末までに数は、おそらく4または5に上がるだろうということ。)(更新:MRI、JRubyの、YARV(通訳:それは今5ですRuby 1.9)、Rubinius、IronRubyなど)。
最初の実装には実際には名前がありません。そのため、それを参照するのは非常に厄介であり、非常に煩わしく混乱します。Rubyプログラミング言語の機能と特定のRuby実装との間で際限なく混乱が生じるため、「Ruby」と呼ばれることが多く、名前がないことよりも迷惑で混乱します。
「MRI」(「MatzのRuby実装」のこと)、CRuby、MatzRubyとも呼ばれます。
MRIはインタプリタ内でグリーンスレッドとしてRubyスレッドを実装します。残念ながら、これらのスレッドを並行してスケジュールすることはできません。一度に実行できるスレッドは1つだけです。
ただし、Rubyスレッドと並行して任意の数のCスレッド(POSIXスレッドなど)を実行できるため、独自のスレッドを作成する外部CライブラリまたはMRI C拡張機能を引き続き並行して実行できます。
2番目の実装はYARV(「Yet Another Ruby VM」の略)です。YARVはRubyスレッドをPOSIXまたはWindows NTスレッドとして実装しますが、グローバルインタープリターロック(GIL)を使用して、一度に1つのRubyスレッドのみを実際にスケジュールできるようにします。
MRIと同様に、Cスレッドは実際にはRubyスレッドと並行して実行できます。
将来的には、GIL がよりきめ細かなロックに分解され、実際により多くのコードを並行して実行できるようになる可能性がありますが、それは遠く離れており、まだ計画されていません。
JRuby はRubyスレッドをネイティブスレッドとして実装します。JVMの場合の「ネイティブスレッド」は明らかに「JVMスレッド」を意味します。JRubyはそれらに追加のロックを課しません。そのため、これらのスレッドが実際に並行して実行できるかどうかは、JVMに依存します。JVMによっては、OSスレッドとして実装されるJVMスレッドと、グリーンスレッドとして実装されるJVMスレッドがあります。(Sun / Oracleの主流のJVMは、JDK 1.3以降、OSスレッドのみを使用しています)
XRubyはまた、RubyスレッドをJVMスレッドとして実装します。更新:XRubyは死んでいます。
IronRuby はRubyスレッドをネイティブスレッドとして実装します。CLRの場合の「ネイティブスレッド」は明らかに「CLRスレッド」を意味します。IronRubyはそれらに追加のロックを課しません。そのため、CLRがサポートしている限り、それらは並行して実行する必要があります。
Ruby.NETは、RubyスレッドもCLRスレッドとして実装しています。更新: Ruby.NETは死んでいます。
Rubinius は、Rubyスレッドを仮想マシン内のグリーンスレッドとして実装します。より正確には、Rubinius VMは、「タスク」と呼ばれる非常に軽量で非常に柔軟な並行性/並列性/非ローカル制御フロー構造、およびその他すべての並行性構造(このディスカッションのスレッドだけでなく、継続、アクターなど)もエクスポートします。)タスクを使用して、純粋なRubyで実装されます。
Rubiniusは(現在のところ)スレッドを並行してスケジュールすることはできませんが、それはそれほど大きな問題ではありません。Rubiniusは、1つのRubiniusプロセス内で複数のPOSIXスレッドで複数のVMインスタンスを並行して実行できます。スレッドは実際にはRubyで実装されているため、他のRubyオブジェクトと同様に、シリアル化して別のPOSIXスレッドの別のVMに送信できます。(これは、BEAM Erlang VMがSMP同時実行に使用するモデルと同じです。RubiniusActorsにはすでに実装されています。)
更新:この回答のルビニウスに関する情報は、もう存在しないShotgun VMに関するものです。「新しい」C ++ VMは、複数のVM(Erlang / BEAMスタイル)にまたがってスケジュールされたグリーンスレッドを使用せず、たとえばCLR、Monoで採用されているのと同じように、複数のネイティブOSスレッドモデルを持つ従来の単一VMを使用します。 、そしてほとんどすべてのJVM。
MacRubyは、Objective-Cランタイム、CoreFoundation、Cocoaフレームワークの上にYARVの移植として始まりました。現在、YARVから大幅に分岐していますが、現在のところ、YARVと同じスレッドモデルを共有しています。 更新: MacRubyは非推奨と宣言されたアップルのガベージコレクターに依存しており、MacOSXの今後のバージョンでは削除されます。MacRubyはアンデッドです。
Cardinalは、Parrot仮想マシンの 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コアライブラリには、別のRubyプロセスを簡単にフォークできる
メソッドを備えたProcess
モジュールが含まれています。また、Ruby標準ライブラリには分散Ruby(dRuby / dRb)ライブラリが含まれているため、
Rubyコードを同じマシン上だけでなくネットワーク全体にも複数のプロセスに簡単に分散できます。Process.fork
Ruby 1.8には緑のスレッドしかありません。実際の「OSレベル」のスレッドを作成する方法はありません。しかし、ruby 1.9には、fiberと呼ばれる新機能が追加され、実際のOSレベルのスレッドを作成できるようになります。残念ながら、Ruby 1.9はまだベータ版であり、数か月以内に安定する予定です。
もう1つの方法は、JRubyを使用することです。JRubyはスレッドをOSレベルのアドとして実装します。その中に「グリーンスレッド」はありません。JRubyの最新バージョンは1.1.4で、Ruby 1.8と同等です。
それは実装に依存します:
Rubyは持っているクロージャを通りBlocks
、lambdas
そしてProcs
。JRubyのクロージャーと複数のコアを最大限に活用するには、Javaのエグゼキューターが便利です。MacRubyの場合、GCDのキューが好きです。
実際の「OSレベル」のスレッド
を作成できることは、並列処理に複数のCPUコアを使用できることを意味しないことに注意してください。以下の例を見てください。
これは、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スレッドと OmnirefのCのグローバルインタープリターロックは、Rubyスレッドが並列実行されない理由をソースコードで説明しています。
「システムモニター」がこの質問に答えるようにします。どちらの場合も、i7(4ハイパースレッドコア)マシンで実行されている8つのRubyスレッドで同じコード(下記の素数を計算します)を実行しています...最初の実行は次のとおりです。
jruby 1.5.6(ruby 1.8.7パッチレベル249)(2014-02-03 6586)(OpenJDK 64ビットサーバーVM 1.7.0_75)[amd64-java]
2つ目は次のとおりです。
ruby 2.1.2p95(2014-05-08)[x86_64-linux-gnu]
興味深いことに、JRubyスレッドのCPUは高くなりますが、解釈されたRubyの完了までの時間はわずかに短くなります。グラフからは少しわかりにくいですが、2回目の実行(Rubyを解釈)では、CPUの約1/2を使用します(ハイパースレッディングなし?)
def eratosthenes(n)
nums = [nil, nil, *2..n]
(2..Math.sqrt(n)).each do |i|
(i**2..n).step(i){|m| nums[m] = nil} if nums[i]
end
nums.compact
end
MAX_PRIME=10000000
THREADS=8
threads = []
1.upto(THREADS) do |num|
puts "Starting thread #{num}"
threads[num]=Thread.new { eratosthenes MAX_PRIME }
end
1.upto(THREADS) do |num|
threads[num].join
end
MRIを使用している場合は、拡張機能として、またはruby-inline gemを使用して、スレッド化されたコードをCで記述できます。
Rubyでプロダクションレベルのシステム(ベータ版を採用できない場合)のために並列処理が本当に必要な場合は、プロセスがおそらくより良い代替案です。
しかし、まず間違いなくJRubyでスレッドを試す価値があります。
また、Rubyでのスレッディングの将来に興味がある場合は、この記事が役立つかもしれません。
Parallel.map(['a','b','c'], :in_processes=>3){...
LindaのRuby実装であるRinda(並列処理と分散コンピューティングのパラダイム)に関する情報は、次のとおりです。http://charmalloc.blogspot.com/2009/12/linda-tuples-rinda-drb-parallel.html
その回答を編集できなかったため、ここに新しい返信を追加してください。
アップデート(2017-05-08)
この記事は非常に古く、情報は現在(2017年)のトレッドに従っていません。以下は補足です。
OpalはRubyからJavaScriptへのソースからソースへのコンパイラです。Ruby corelibの実装もあり、現在非常にアクティブな開発版であり、それに取り組んでいる(フロントエンド)フレームワークが多数存在します。そして生産準備ができています。JavaScriptに基づいているため、並列スレッドをサポートしていません。
trufflerubyは、Rubyプログラミング言語の高性能実装です。Oracle LabsによってGraalVM上に構築されたTruffleRubyは、JRubyのフォークであり、Rubiniusプロジェクトからのコードと結合し、Ruby、MRIの標準実装からのコードも含みます。このバージョンのルビーはパフォーマンスのために生まれたように見えますが、並列スレッドをサポートするかどうかはわかりませんが、そうすべきだと思います。