Hat Ruby echtes Multithreading?


295

Ich kenne das "kooperative" Einfädeln von Rubin mit grünen Fäden . Wie kann ich in meiner Anwendung echte Threads auf Betriebssystemebene erstellen, um mehrere CPU-Kerne für die Verarbeitung zu verwenden?

Antworten:


612

Aktualisiert mit Jörgs Kommentar vom September 2011

Sie scheinen hier zwei sehr unterschiedliche Dinge zu verwechseln : die Ruby-Programmiersprache und das spezifische Threading-Modell einer bestimmten Implementierung der Ruby-Programmiersprache. Derzeit gibt es rund 11 verschiedene Implementierungen der Ruby-Programmiersprache mit sehr unterschiedlichen und einzigartigen Threading-Modellen.

(Leider sind nur zwei dieser 11 Implementierungen tatsächlich für die Produktion bereit, aber bis Ende des Jahres wird diese Zahl wahrscheinlich auf vier oder fünf steigen.) ( Update : Es ist jetzt 5: MRI, JRuby, YARV (der Dolmetscher) für Ruby 1.9), Rubinius und IronRuby).

  1. Die erste Implementierung hat eigentlich keinen Namen, was es ziemlich umständlich macht, darauf zu verweisen, und ist wirklich nervig und verwirrend. Es wird am häufigsten als "Ruby" bezeichnet, was noch ärgerlicher und verwirrender ist, als keinen Namen zu haben, da es zu endlosen Verwirrungen zwischen den Funktionen der Ruby-Programmiersprache und einer bestimmten Ruby-Implementierung führt.

    Es wird manchmal auch als "MRI" (für "Matz's Ruby Implementation"), CRuby oder MatzRuby bezeichnet.

    MRI implementiert Ruby Threads als Green Threads in seinem Interpreter . Leider können diese Threads nicht parallel geplant werden, sondern es kann jeweils nur ein Thread ausgeführt werden.

    Es können jedoch beliebig viele C-Threads (POSIX-Threads usw.) parallel zum Ruby-Thread ausgeführt werden, sodass externe C-Bibliotheken oder MRI-C-Erweiterungen, die eigene Threads erstellen, weiterhin parallel ausgeführt werden können.

  2. Die zweite Implementierung ist YARV (kurz für "Yet Another Ruby VM"). YARV implementiert Ruby-Threads als POSIX- oder Windows NT-Threads . Es verwendet jedoch eine globale Interpreter-Sperre (GIL), um sicherzustellen, dass jeweils nur ein Ruby-Thread geplant werden kann.

    Wie MRI, C Threads können tatsächlich parallel zu Ruby - Threads laufen.

    In Zukunft ist es möglich, dass die GIL könnte in feinkörnigen Schlösser erhalten abgebaut, so dass mehr und mehr Code , der es tatsächlich parallel laufen, aber das ist so weit weg, ist es nicht einmal geplant noch.

  3. JRuby implementiert Ruby-Threads als native Threads , wobei "native Threads" im Fall der JVM offensichtlich "JVM-Threads" bedeutet. JRuby schreibt ihnen keine zusätzliche Verriegelung vor. Ob diese Threads tatsächlich parallel ausgeführt werden können, hängt von der JVM ab: Einige JVMs implementieren JVM-Threads als Betriebssystem-Threads und andere als grüne Threads. (Die Mainstream-JVMs von Sun / Oracle verwenden seit JDK 1.3 ausschließlich Betriebssystem-Threads.)

  4. XRuby auch implementiert Ruby - Threads als JVM Threads . Update : XRuby ist tot.

  5. IronRuby implementiert Ruby-Threads als native Threads , wobei "native Threads" im Fall der CLR offensichtlich "CLR-Threads" bedeutet. IronRuby schreibt ihnen keine zusätzliche Sperre vor, daher sollten sie parallel ausgeführt werden, solange Ihre CLR dies unterstützt.

  6. Ruby.NET auch implementiert Ruby - Threads als CLR Threads . Update: Ruby.NET ist tot.

  7. Rubinius implementiert Ruby-Threads als grüne Threads in seiner virtuellen Maschine . Genauer gesagt: Die Rubinius-VM exportiert ein sehr leichtes, sehr flexibles Parallelitäts- / Parallelitäts- / nicht lokales Kontrollflusskonstrukt, das als " Aufgabe " bezeichnet wird, und alle anderen Parallelitätskonstrukte (Themen in dieser Diskussion, aber auch Fortsetzungen , Akteure und andere Dinge) ) werden mit Aufgaben in reinem Ruby implementiert.

    Rubinius kann Threads (derzeit) nicht parallel planen, was jedoch kein allzu großes Problem darstellt: Rubinius kann bereits mehrere VM-Instanzen in mehreren POSIX-Threads innerhalb eines Rubinius-Prozesses parallel ausführen . Da Threads tatsächlich in Ruby implementiert sind, können sie wie jedes andere Ruby-Objekt serialisiert und an eine andere VM in einem anderen POSIX-Thread gesendet werden. (Dies ist das gleiche Modell, das die BEAM Erlang VM für die SMP-Parallelität verwendet. Es ist bereits für Rubinius Actors implementiert .)

    Update : Die Informationen zu Rubinius in dieser Antwort beziehen sich auf die Shotgun VM, die nicht mehr existiert. Die "neue" C ++ - VM verwendet keine grünen Threads, die für mehrere VMs geplant sind (z. B. Erlang / BEAM-Stil), sondern eine traditionellere Einzel-VM mit mehreren nativen Betriebssystem-Threads, wie sie beispielsweise von der CLR Mono verwendet wird und so ziemlich jede JVM.

  8. MacRuby begann als Port von YARV zusätzlich zu den Objective-C Runtime- und CoreFoundation- und Cocoa-Frameworks. Es hat sich jetzt erheblich von YARV unterschieden, aber AFAIK teilt derzeit immer noch das gleiche Threading-Modell mit YARV . Update: MacRuby hängt vom Apfel-Garbage-Collector ab, der für veraltet erklärt wird und in späteren Versionen von MacOSX entfernt wird. MacRuby ist untot.

  9. Cardinal ist eine Ruby-Implementierung für die Parrot Virtual Machine . Es werden noch keine Threads implementiert. Wenn dies jedoch der Fall ist, werden sie wahrscheinlich als Papageienthreads implementiert . Update : Kardinal scheint sehr inaktiv / tot zu sein.

  10. MagLev ist eine Ruby-Implementierung für die GemStone / S Smalltalk-VM . Ich habe keine Informationen darüber, welches Threading-Modell GemStone / S verwendet, welches Threading-Modell MagLev verwendet oder ob Threads noch implementiert sind (wahrscheinlich nicht).

  11. HotRuby ist keine vollständige Ruby-Implementierung für sich. Es ist eine Implementierung einer YARV-Bytecode-VM in JavaScript. HotRuby unterstützt (noch?) Keine Threads, und wenn dies der Fall ist, können sie nicht parallel ausgeführt werden, da JavaScript keine echte Parallelität unterstützt. Es gibt jedoch eine ActionScript-Version von HotRuby, und ActionScript unterstützt möglicherweise tatsächlich Parallelität. Update : HotRuby ist tot.

Leider sind nur zwei dieser 11 Ruby-Implementierungen tatsächlich produktionsbereit: MRI und JRuby.

Wenn Sie also echte parallele Threads wünschen, ist JRuby derzeit Ihre einzige Wahl - nicht, dass dies eine schlechte Wahl ist: JRuby ist tatsächlich schneller als MRI und wahrscheinlich stabiler.

Andernfalls besteht die "klassische" Ruby-Lösung darin, Prozesse anstelle von Threads für die Parallelität zu verwenden. Die Ruby Core Library enthält das ProcessModul mit der Process.fork Methode , die es kinderleicht macht, einen anderen Ruby-Prozess abzuschalten. Außerdem enthält die Ruby-Standardbibliothek die Distributed Ruby- Bibliothek (dRuby / dRb) , mit der Ruby-Code trivial über mehrere Prozesse verteilt werden kann, nicht nur auf demselben Computer, sondern auch über das Netzwerk.


1
aber mit Gabel wird die Verwendung auf jruby brechen ... nur sagen
akostadinov

1
Dies ist eine großartige Antwort. Es ist jedoch viel Link Rot ausgesetzt. Ich weiß jedoch nicht, wohin sich diese Ressourcen bewegt haben könnten.
BlackVegetable

28

Ruby 1.8 hat nur grüne Threads. Es gibt keine Möglichkeit, einen echten Thread auf Betriebssystemebene zu erstellen. Ruby 1.9 verfügt jedoch über eine neue Funktion namens Fasern, mit der Sie tatsächliche Threads auf Betriebssystemebene erstellen können. Leider befindet sich Ruby 1.9 noch in der Beta-Phase. Es ist geplant, dass es in ein paar Monaten stabil sein wird.

Eine andere Alternative ist die Verwendung von JRuby. JRuby implementiert Threads als Theads auf Betriebssystemebene. Es sind keine "grünen Threads" enthalten. Die neueste Version von JRuby ist 1.1.4 und entspricht Ruby 1.8


35
Es ist falsch, dass Ruby 1.8 nur grüne Threads hat. Einige Implementierungen von Ruby 1.8 haben native Threads: JRuby, XRuby, Ruby.NET und IronRuby. Fasern erlauben nicht die Erstellung nativer Threads, sie sind leichter als Threads. Sie sind eigentlich Halbkoroutinen, dh sie sind kooperativ.
Jörg W Mittag

19
Ich denke, es ist ziemlich offensichtlich aus Joshs Antwort, dass er Ruby 1.8 die Laufzeit, auch bekannt als MRI, und nicht Ruby 1.8 die Sprache meint, wenn er Ruby 1.8 sagt.
Theo

@ Theo Es ist auch offensichtlich, dass er Konzepte in seiner Antwort durcheinander bringt. Fasern sind keine Möglichkeit, native Threads zu erstellen, wie bereits erwähnt. Sie sind sogar noch leichter als Threads, und derzeit hat Cruby native Threads, jedoch mit GIL.
Foo Bar Zoo

8

Es kommt auf die Implementierung an:

  • MRT hat nicht, YARV ist näher.
  • JRuby und MacRuby haben.




Ruby hat Verschlüsse als Blocks, lambdasund Procs. Um die Vorteile von Schließungen und mehreren Kernen in JRuby voll auszuschöpfen, sind die Java-Executoren hilfreich. Für MacRuby mag ich die Warteschlangen von GCD .

Beachten Sie, dass das Erstellen von echten Threads auf Betriebssystemebene nicht bedeutet, dass Sie mehrere CPU-Kerne für die parallele Verarbeitung verwenden können. Schauen Sie sich die folgenden Beispiele an.

Dies ist die Ausgabe eines einfachen Ruby-Programms, das mit Ruby 2.1.0 3 Threads verwendet:

(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 

Wie Sie hier sehen können, gibt es vier Betriebssystem-Threads, jedoch wird nur der mit Status Rausgeführt. Dies liegt an einer Einschränkung bei der Implementierung von Rubys Threads.



Gleiches Programm, jetzt mit JRuby. Sie können drei Threads mit Status sehen R, was bedeutet, dass sie parallel ausgeführt werden.

(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 


Das gleiche Programm, jetzt mit MacRuby. Es laufen auch drei Threads parallel. Dies liegt daran, dass MacRuby-Threads POSIX-Threads sind ( echte Betriebssystemebene ) und keine GVL vorhanden ist

(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 


Wieder das gleiche Programm, aber jetzt mit der guten alten MRT. Aufgrund der Tatsache, dass diese Implementierung grüne Threads verwendet, wird nur ein Thread angezeigt

(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



Wenn Sie an Ruby-Multithreading interessiert sind, könnte mein Bericht Debuggen paralleler Programme mit Fork-Handlern interessant sein.
Für einen allgemeineren Überblick über die Ruby-Interna ist Ruby Under a Microscope eine gute Lektüre.
Ebenfalls, erklären Ruby-Threads und die globale Interpreter-Sperre in C in Omniref im Quellcode, warum Ruby-Threads nicht parallel ausgeführt werden.


Mit RMI meinen Sie MRT?
Mayuresh Srivastava

4

Wie wäre es mit drb ? Es ist kein echtes Multithreading, sondern die Kommunikation zwischen mehreren Prozessen, aber Sie können es jetzt in Version 1.8 verwenden und es ist ziemlich reibungsarm.


3

Ich werde den "System Monitor" diese Frage beantworten lassen. Ich führe den gleichen Code (unten, der Primzahlen berechnet) mit 8 Ruby-Threads aus, die in beiden Fällen auf einem i7-Computer (4 Hyperthreaded-Core) ausgeführt werden. Der erste Lauf erfolgt mit:

jruby 1.5.6 (Ruby 1.8.7 Patchlevel 249) (2014-02-03 6586) (OpenJDK 64-Bit Server VM 1.7.0_75) [amd64-java]

Der zweite ist mit:

ruby 2.1.2p95 (08.05.2014) [x86_64-linux-gnu]

Interessanterweise ist die CPU für JRuby-Threads höher, aber die Zeit bis zur Fertigstellung ist für den interpretierten Ruby etwas kürzer. Es ist schwer anhand des Diagramms zu erkennen, aber der zweite (interpretierte Ruby) Lauf verwendet ungefähr die Hälfte der CPUs (kein Hyperthreading?)

Geben Sie hier die Bildbeschreibung ein

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

Wenn Sie MRT verwenden, können Sie den Thread-Code in C entweder als Erweiterung oder mithilfe des Ruby-Inline-Edelsteins schreiben.


1

Wenn Sie in Ruby wirklich Parallelität für ein System auf Produktionsebene benötigen (in dem Sie keine Beta-Version verwenden können), sind Prozesse wahrscheinlich die bessere Alternative.
Aber es lohnt sich auf jeden Fall, zuerst Threads unter JRuby auszuprobieren.

Auch wenn Sie an einem zukünftigen Threading unter Ruby interessiert sind, könnte dieser Artikel hilfreich sein.


JRuby ist eine gute Option. Für die parallele Verarbeitung mit Prozessen mag ich github.com/grosser/parallel Parallel.map(['a','b','c'], :in_processes=>3){...
user454322


1

Da diese Antwort nicht bearbeitet werden konnte, fügen Sie hier eine neue Antwort hinzu.

Update (08.05.2017)

Dieser Artikel ist sehr alt und die Informationen folgen nicht dem aktuellen (2017) Profil. Es folgt eine Ergänzung:

  1. Opal ist ein Ruby-zu-JavaScript-Compiler von Quelle zu Quelle. Es hat auch eine Implementierung der Ruby Corelib, es ist derzeit sehr aktiv entwickelt und es gibt eine Menge (Frontend-) Frameworks, die daran gearbeitet haben. und produktionsbereit. Da es auf Javascript basiert, werden parallele Threads nicht unterstützt.

  2. truffleruby ist eine Hochleistungsimplementierung der Programmiersprache Ruby. TruffleRuby basiert auf GraalVM von Oracle Labs und ist eine Abzweigung von JRuby, die es mit Code aus dem Rubinius-Projekt kombiniert und auch Code aus der Standardimplementierung von Ruby, MRI, enthält. Die Live-Entwicklung ist noch nicht produktionsbereit. Diese Version Ruby scheint für die Leistung geboren zu sein, ich weiß nicht, ob parallele Threads unterstützt werden, aber ich denke, es sollte.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.