Il ruby ha un vero multithreading?

Conosco il threading “cooperativo” del ruby usando fili verdi . Come posso creare veri thread “a livello di sistema operativo” nella mia applicazione al fine di utilizzare più core CPU per l’elaborazione?

Aggiornato con il commento di Jörg del settembre 2011

Sembra che tu stia confondendo due cose molto diverse qui: il Linguaggio di programmazione di Ruby e il modello specifico di threading di una specifica implementazione del Linguaggio di Programmazione di Ruby. Ci sono attualmente circa 11 diverse implementazioni del linguaggio di programmazione Ruby, con modelli di threading molto diversi e unici.

(Sfortunatamente, solo due di quelle 11 implementazioni sono effettivamente pronte per l’uso in produzione, ma entro la fine dell’anno quel numero andrà probabilmente fino a quattro o cinque.) ( Aggiornamento : è ora 5: MRI, JRuby, YARV (l’interprete per Ruby 1.9), Rubinius e IronRuby).

  1. La prima implementazione in realtà non ha un nome, il che rende piuttosto complicato farvi riferimento ed è davvero fastidioso e confuso. Viene spesso indicato come “Ruby”, che è ancora più fastidioso e confuso di non avere un nome, perché porta a una confusione infinita tra le funzionalità del Linguaggio di programmazione di Ruby e una particolare implementazione di Ruby.

    A volte viene anche chiamato “MRI” (per “Matz’s Ruby Implementation”), CRuby o MatzRuby.

    MRI implementa Threads Ruby come Green Thread all’interno del suo interprete . Sfortunatamente, non consente la programmazione in parallelo di questi thread, ma possono eseguire solo un thread alla volta.

    Tuttavia, qualsiasi numero di thread C (thread POSIX, ecc.) Può essere eseguito parallelamente al thread Ruby, quindi le librerie C esterne o le estensioni MRI C che creano thread possono comunque essere eseguite in parallelo.

  2. La seconda implementazione è YARV (abbreviazione di “Yet Another Ruby VM”). YARV implementa Threads Ruby come thread POSIX o Windows NT , tuttavia utilizza un Global Interpreter Lock (GIL) per garantire che solo un thread Ruby possa essere programmato in qualsiasi momento.

    Come la risonanza magnetica, i thread C possono effettivamente essere eseguiti parallelamente a thread in ruby.

    In futuro, è ansible che il GIL venga suddiviso in più blocchi a grana fine, consentendo così a un numero sempre maggiore di codice di funzionare in parallelo, ma è così lontano che non è ancora pianificato .

  3. JRuby implementa Threads Ruby come Thread Nativi , dove “Thread Nativi” nel caso della JVM significa ovviamente “Thread JVM”. JRuby non impone alcun blocco aggiuntivo su di loro. Quindi, se questi thread possono effettivamente essere eseguiti in parallelo dipende dalla JVM: alcuni JVM implementano Thread JVM come thread OS e alcuni come Green Threads. (Le JVM mainstream di Sun / Oracle utilizzano esclusivamente i thread del sistema operativo da JDK 1.3)

  4. XRuby implementa anche Threads Ruby come thread JVM . Aggiornamento : XRuby è morto.

  5. IronRuby implementa Threads Ruby come Thread Nativi , dove “Thread Nativi” nel caso del CLR ovviamente significa “Thread CLR”. IronRuby non impone alcun blocco aggiuntivo su di essi, quindi, devono essere eseguiti in parallelo, a condizione che il tuo CLR lo supporti.

  6. Ruby.NET implementa anche Threads Ruby come Thread CLR . Aggiornamento: Ruby.NET è morto.

  7. Rubinio implementa i fili di ruby come fili verdi all’interno della sua macchina virtuale . Più precisamente: la VM di Rubinius esporta un costrutto di stream di controllo concorrente / parallelismo / non locale molto leggero, molto flessibile, chiamato ” Task ” e tutti gli altri costrutti di concorrenza (Discussioni in questa discussione, ma anche Continuazioni , Attori e altre cose ) sono implementati in puro Ruby, usando Task.

    Rubinius non può (attualmente) programmare Thread in parallelo, tuttavia, aggiungendo che non è un gran problema: Rubinius può già eseguire diverse istanze VM in più thread POSIX in parallelo , all’interno di un processo Rubinius. Poiché i thread sono effettivamente implementati in Ruby, possono, come qualsiasi altro object Ruby, essere serializzati e inviati a una VM diversa in un diverso thread POSIX. (È lo stesso modello utilizzato da BEAM Erlang VM per la concorrenza SMP ed è già implementato per gli attori Rubinius .)

    Aggiornamento : Le informazioni su Rubinius in questa risposta riguardano la VM shotgun, che non esiste più. La “nuova” C ++ VM non utilizza thread verdi pianificati su più VM (es. Stile Erlang / BEAM), utilizza una singola VM più tradizionale con più modelli di thread OS nativi, proprio come quella utilizzata, ad esempio, da CLR, Mono e praticamente ogni JVM.

  8. MacRuby è iniziato come una porta di YARV su Objective-C Runtime e CoreFoundation e Cocoa Frameworks. Ora è notevolmente diverso da YARV, ma AFAIK attualmente condivide ancora lo stesso modello di threading con YARV . Aggiornamento: MacRuby dipende dal garbage collector delle mele dichiarato deprecato e rimosso nelle versioni successive di MacOSX, MacRuby è un non morto.

  9. Cardinal è un’applicazione di Ruby per Parrot Virtual Machine . Non implementa ancora i thread, tuttavia, quando lo fa, probabilmente li implementerà come thread Parrot . Aggiornamento : il cardinale sembra molto inattivo / morto.

  10. MagLev è un’applicazione di Ruby per GemStone / S Smalltalk VM . Non ho informazioni sul modello di threading utilizzato da GemStone / S, sul modello di threading che MagLev utilizza o anche se i thread sono ancora implementati (probabilmente no).

  11. HotRuby non è una completa implementazione di Ruby. È un’implementazione di una VM bytecode YARV in JavaScript. HotRuby non supporta i thread (ancora?) E quando lo fa, non potranno essere eseguiti in parallelo, perché JavaScript non supporta il vero parallelismo. Esiste tuttavia una versione di ActionScript di HotRuby e ActionScript potrebbe effettivamente supportare il parallelismo. Aggiornamento : HotRuby è morto.

Sfortunatamente, solo due di queste 11 implementazioni di Ruby sono effettivamente pronte per la produzione: MRI e JRuby.

Quindi, se vuoi veri thread paralleli, JRuby è attualmente la tua unica scelta, non quella sbagliata: JRuby è in realtà più veloce della risonanza magnetica e probabilmente più stabile.

Altrimenti, la soluzione “classica” di Ruby consiste nell’utilizzare i processi anziché i thread per il parallelismo. La Ruby Core Library contiene il modulo Process con il metodo Process.fork , che rende Process.fork il fork di un altro processo Ruby. Inoltre, la Ruby Standard Library contiene la libreria Ruby distribuito (dRuby / dRb) , che consente di distribuire banalmente il codice Ruby su più processi, non solo sulla stessa macchina ma anche attraverso la rete.

Ruby 1.8 ha solo thread verdi, non c’è modo di creare un vero thread “OS-level”. Ma Ruby 1.9 avrà una nuova funzionalità chiamata fibre, che ti consentirà di creare thread a livello di sistema operativo. Sfortunatamente, Ruby 1.9 è ancora in versione beta, è programmato per essere stabile in un paio di mesi.

Un’altra alternativa è usare JRuby. JRuby implementa i thread come livelli del sistema operativo, non ci sono “fili verdi” al suo interno. L’ultima versione di JRuby è la 1.1.4 ed è equivalente a Ruby 1.8

Dipende dall’implementazione:

  • RMI non ha, YARV è più vicino.
  • JRuby e MacRuby hanno.

Ruby ha chiusure come Blocks , lambdas e Procs . Per sfruttare appieno le chiusure e i core multipli di JRuby, gli esecutori di Java tornano utili; per MacRuby mi piacciono le code di GCD .

Si noti che, essere in grado di creare veri thread “a livello di OS” non implica che sia ansible utilizzare più core CPU per l’elaborazione parallela. Guarda gli esempi qui sotto.

Questo è l’output di un semplice programma Ruby che usa 3 thread usando Ruby 2.1.0:

 (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 

Come puoi vedere qui, ci sono quattro thread del sistema operativo, tuttavia è in esecuzione solo quello con stato R Ciò è dovuto a una limitazione nel modo in cui i thread di Ruby sono implementati.


Stesso programma, ora con JRuby. Puoi vedere tre thread con stato R , il che significa che sono in esecuzione in parallelo.

 (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 

Lo stesso programma, ora con MacRuby. Ci sono anche tre thread in esecuzione in parallelo. Questo perché i thread MacRuby sono thread POSIX ( veri thread “a livello di OS” ) e non c’è 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 

Ancora una volta, lo stesso programma ma ora con il buon vecchio RMI. A causa del fatto che questa implementazione utilizza thread verdi, viene visualizzato solo un thread

 (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 

Se ti interessa il multi-threading di Ruby potresti trovare il mio rapporto Debug dei programmi paralleli usando i gestori di fork interessanti.
Per una panoramica più generale degli interni di Ruby Ruby Under a Microscope è una buona lettura.
Inoltre, Ruby Threads e Global Interpreter Lock in C in Omniref spiegano nel codice sorgente perché i thread di Ruby non vengono eseguiti in parallelo.

Che ne dici di usare drb ? Non è vero multi-threading ma comunicazione tra diversi processi, ma puoi usarlo ora in 1.8 ed è abbastanza basso.

Lascerò che il “Monitor di sistema” risponda a questa domanda. Sto eseguendo lo stesso codice (di seguito, che calcola i numeri primi) con 8 thread Ruby eseguiti su una macchina i7 (4 hyperthreaded-core) in entrambi i casi … la prima esecuzione è con:

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]

Il secondo è con:

ruby 2.1.2p95 (2014-05-08) [x86_64-linux-gnu]

È interessante notare che la CPU è più alta per i thread JRuby, ma il tempo di completamento è leggermente più breve per il Ruby interpretato. È un po ‘difficile da capire dal grafico, ma il secondo (interpretato da Ruby) usa circa 1/2 delle CPU (nessun hyperthreading?)

inserisci la descrizione dell'immagine qui

 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 

Se si utilizza la risonanza magnetica, è ansible scrivere il codice filettato in C come un’estensione o utilizzando la gem ruby-inline.

Se hai davvero bisogno del parallelismo in Ruby per un sistema a livello di produzione (dove non puoi utilizzare una beta) i processi sono probabilmente un’alternativa migliore.
Ma è decisamente la pena provare prima i thread sotto JRuby.

Inoltre, se sei interessato al futuro del threading in Ruby, potresti trovare utile questo articolo .

Ecco alcune informazioni su Rinda che è l’implementazione di Ruby di Linda (elaborazione parallela e paradigma di calcolo distribuito) http://charmalloc.blogspot.com/2009/12/linda-tuples-rinda-drb-parallel.html

Perché non è ansible modificare quella risposta, quindi aggiungere una nuova risposta qui.

Aggiornamento (2017/05/08)

Questo articolo è molto vecchio e le informazioni non seguono il battistrada corrente (2017). Di seguito è riportato un supplemento:

  1. Opal è un compilatore Ruby to JavaScript source-to-source. Ha anche un’implementazione del Corelib di Ruby, è uno sviluppo molto attivo ed esiste una grande quantità di framework (frontend) su cui è basato. e la produzione è pronta. Perché base su javascript, non supporta i thread paralleli.

  2. truffleruby è un’implementazione ad alte prestazioni del linguaggio di programmazione Ruby. Costruito su GraalVM da Oracle Labs, TruffleRuby è un fork di JRuby, che lo combina con il codice del progetto Rubinius e contiene anche il codice dall’implementazione standard di Ruby, MRI, ancora in sviluppo live, non in produzione. Questa versione ruby sembra nata per le prestazioni, non so se supporta i thread paralleli, ma penso che dovrebbe.