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?
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:
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).
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.
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.
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.)
XRuby auch implementiert Ruby - Threads als JVM Threads . Update : XRuby ist tot.
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.
Ruby.NET auch implementiert Ruby - Threads als CLR Threads . Update: Ruby.NET ist tot.
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.
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.
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.
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).
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 Process
Modul 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.
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
Es kommt auf die Implementierung an:
Ruby hat Verschlüsse als Blocks
, lambdas
und 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 R
ausgefü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.
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?)
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
Wenn Sie MRT verwenden, können Sie den Thread-Code in C entweder als Erweiterung oder mithilfe des Ruby-Inline-Edelsteins schreiben.
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.
Parallel.map(['a','b','c'], :in_processes=>3){...
Hier finden Sie einige Informationen zu Rinda, der Ruby-Implementierung von Linda (Paradigma für parallele Verarbeitung und verteiltes Rechnen). Http://charmalloc.blogspot.com/2009/12/linda-tuples-rinda-drb-parallel.html
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:
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.
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.