Rubyには実際のマルチスレッド機能がありますか?


295

グリーンスレッドを使用したルビの「協調的」スレッドについて知っています。処理に複数のCPUコアを利用するために、アプリケーションに実際の「OSレベル」のスレッドを作成するにはどうすればよいですか?

回答:


612

Jörgの2011年9月のコメントで更新

ここでは、Rubyプログラミング言語と、Rubyプログラミング言語の1つの特定の実装の特定のスレッドモデルという2つの非常に異なるものが混乱しているようです。現在、Rubyプログラミング言語には約11の異なる実装があり、非常に異なる独自のスレッドモデルがあります。

(残念ながら、これらの11個の実装の2つだけでは、実際に生産使用のために準備ができているが、今年末までに数は、おそらく4または5に上がるだろうということ。)(更新:MRI、JRubyの、YARV(通訳:それは今5ですRuby 1.9)、Rubinius、IronRubyなど)。

  1. 最初の実装には実際には名前がありません。そのため、それを参照するのは非常に厄介であり、非常に煩わしく混乱します。Rubyプログラミング言語の機能と特定のRuby実装との間で際限なく混乱が生じるため、「Ruby」と呼ばれることが多く、名前がないことよりも迷惑で混乱します。

    「MRI」(「MatzのRuby実装」のこと)、CRuby、MatzRubyとも呼ばれます。

    MRIはインタプリタ内でグリーンスレッドとしてRubyスレッドを実装します。残念ながら、これらのスレッドを並行してスケジュールすることはできません。一度に実行できるスレッドは1つだけです。

    ただし、Rubyスレッドと並行して任意の数のCスレッド(POSIXスレッドなど)を実行できるため、独自のスレッドを作成する外部CライブラリまたはMRI C拡張機能を引き続き並行して実行できます。

  2. 2番目の実装はYARV(「Yet Another Ruby VM」の略)です。YARVはRubyスレッドをPOSIXまたはWindows NTスレッドとして実装しますが、グローバルインタープリターロック(GIL)を使用して、一度に1つのRubyスレッドのみを実際にスケジュールできるようにします。

    MRIと同様に、Cスレッド実際にはRubyスレッドと並行して実行できます。

    将来的には、GIL よりきめ細かなロックに分解され、実際により多くのコードを並行して実行できるようになる可能性がありますが、それは遠く離れており、まだ計画されていません。

  3. JRuby はRubyスレッドをネイティブスレッドとして実装します。JVMの場合の「ネイティブスレッド」は明らかに「JVMスレッド」を意味します。JRubyはそれらに追加のロックを課しません。そのため、これらのスレッドが実際に並行して実行できるかどうかは、JVMに依存します。JVMによっては、OSスレッドとして実装されるJVMスレッドと、グリーンスレッドとして実装されるJVMスレッドがあります。(Sun / Oracleの主流のJVMは、JDK 1.3以降、OSスレッドのみを使用しています)

  4. XRubyはまた、RubyスレッドをJVMスレッドとして実装します。更新:XRubyは死んでいます。

  5. IronRuby はRubyスレッドをネイティブスレッドとして実装します。CLRの場合の「ネイティブスレッド」は明らかに「CLRスレッド」を意味します。IronRubyはそれらに追加のロックを課しません。そのため、CLRがサポートしている限り、それらは並行して実行する必要があります。

  6. Ruby.NETは、RubyスレッドもCLRスレッドとして実装しています。更新: Ruby.NETは死んでいます。

  7. 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。

  8. MacRubyは、Objective-Cランタイム、CoreFoundation、Cocoaフレームワークの上にYARVの移植として始まりました。現在、YARVから大幅に分岐していますが、現在のところ、YARVと同じスレッドモデルを共有しています。 更新: MacRubyは非推奨と宣言されたアップルのガベージコレクターに依存しており、MacOSXの今後のバージョンでは削除されます。MacRubyはアンデッドです。

  9. Cardinalは、Parrot仮想マシンの Ruby実装です。まだスレッドを実装していませんが、実装すると、Parrot Threadsとして実装される可能性があります。更新:枢機卿は非常に非アクティブ/デッドのようです。

  10. MagLevは、GemStone / S Smalltalk VMの Ruby実装です。GemStone / Sが使用しているスレッドモデル、MagLevが使用しているスレッドモデル、またはスレッドがまだ実装されている場合(おそらくそうではない場合)についての情報はありません。

  11. 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


1
しかし、フォークを使用すると、jrubyでの使用が中断されます...ただ言う
akostadinov 14

1
これは素晴らしい答えです。しかし、それは多くのリンク腐敗の対象となります。これらのリソースがどこに移動したのかはわかりません。
BlackVegetable 2017

28

Ruby 1.8には緑のスレッドしかありません。実際の「OSレベル」のスレッドを作成する方法はありません。しかし、ruby 1.9には、fiberと呼ばれる新機能が追加され、実際のOSレベルのスレッドを作成できるようになります。残念ながら、Ruby 1.9はまだベータ版であり、数か月以内に安定する予定です。

もう1つの方法は、JRubyを使用することです。JRubyはスレッドをOSレベルのアドとして実装します。その中に「グリーンスレッド」はありません。JRubyの最新バージョンは1.1.4で、Ruby 1.8と同等です。


35
Ruby 1.8にグリーンスレッドしかないことは誤りです。Ruby1.8のいくつかの実装には、JRuby、XRuby、Ruby.NET、IronRubyなどのネイティブスレッドがあります。ファイバーは、ネイティブスレッドの作成を許可しません。スレッドよりも軽量です。それらは実際にはセミコルーチンです。つまり、それらは協調的です。
イェルクWミッターク

19
JoshがRuby 1.8と言うとき、彼がRuby 1.8をランタイム、つまりMRIであり、Ruby 1.8ではないということは、Joshの回答から明らかだと思います。
Theo

@テオ彼が彼の答えで概念を台無しにすることも明らかです。すでに述べたように、ファイバーはネイティブスレッドを作成する方法ではありません。ファイバーはスレッドよりも軽量であり、現在のクルービーにはネイティブスレッドがありますが、GILを使用しています。
Foo Bar Zoo

8

それは実装に依存します:

  • MRIにはありません。YARVの方が近いです。
  • JRubyとMacRubyにはあります。




Rubyは持っているクロージャを通りBlockslambdasそして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スレッドが並列実行されない理由をソースコードで説明しています。


RMIとは、MRIのことですか?
Mayuresh Srivastava

4

どのように使用する方法についてDRBの?これは実際のマルチスレッドではなく、複数のプロセス間の通信ですが、1.8で使用できるようになり、摩擦がかなり少なくなっています。


3

「システムモニター」がこの質問に答えるようにします。どちらの場合も、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

1

MRIを使用している場合は、拡張機能として、またはruby-inline gemを使用して、スレッド化されたコードをCで記述できます。


1

Rubyでプロダクションレベルのシステム(ベータ版を採用できない場合)のために並列処理が本当に必要な場合は、プロセスがおそらくより良い代替案です。
しかし、まず間違いなくJRubyでスレッドを試す価値があります。

また、Rubyでのスレッディングの将来に興味がある場合は、この記事が役立つかもしれません。


JRubyは良いオプションです。github.com/grosser/parallelの ようなプロセスを使用した並列処理の場合 Parallel.map(['a','b','c'], :in_processes=>3){...
user454322


1

その回答を編集できなかったため、ここに新しい返信を追加してください。

アップデート(2017-05-08)

この記事は非常に古く、情報は現在(2017年)のトレッドに従っていません。以下は補足です。

  1. OpalはRubyからJavaScriptへのソースからソースへのコンパイラです。Ruby corelibの実装もあり、現在非常にアクティブな開発版であり、それに取り組んでいる(フロントエンド)フレームワークが多数存在します。そして生産準備ができています。JavaScriptに基づいているため、並列スレッドをサポートしていません。

  2. trufflerubyは、Rubyプログラミング言語の高性能実装です。Oracle LabsによってGraalVM上に構築されたTruffleRubyは、JRubyのフォークであり、Rubiniusプロジェクトからのコードと結合し、Ruby、MRIの標準実装からのコードも含みます。このバージョンのルビーはパフォーマンスのために生まれたように見えますが、並列スレッドをサポートするかどうかはわかりませんが、そうすべきだと思います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.