Ruby ha un vero multithreading?


295

Conosco la filettatura "cooperativa" del rubino 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?

Risposte:


612

Aggiornato con il commento di settembre 2011 di Jörg

Sembra che tu confonda due cose molto diverse qui: il Ruby Programming Language e il modello di threading specifico di una specifica implementazione del Ruby Programming Language. Attualmente ci sono circa 11 diverse implementazioni del linguaggio di programmazione Ruby, con modelli di threading molto diversi e unici.

(Sfortunatamente, solo due di queste 11 implementazioni sono effettivamente pronte per l'uso in produzione, ma entro la fine dell'anno quel numero probabilmente salirà a quattro o cinque.) ( Aggiornamento : ora sono 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 imbarazzante fare riferimento ad essa ed è davvero fastidioso e confuso. Viene spesso definito "Ruby", che è ancora più fastidioso e confuso che non avere un nome, perché porta a una confusione senza fine tra le caratteristiche del Linguaggio di programmazione Ruby e una particolare Implementazione Ruby.

    A volte viene anche chiamato "MRI" (per "Matz's Ruby Implementation"), CRuby o MatzRuby.

    La risonanza magnetica implementa i thread di Ruby come thread verdi all'interno del suo interprete . Sfortunatamente, non consente di programmare in parallelo quei thread, ma possono eseguire solo un thread alla volta.

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

  2. La seconda implementazione è YARV (abbreviazione di "Yet Another Ruby VM"). YARV implementa i thread Ruby come thread POSIX o Windows NT , tuttavia utilizza un Global Interpreter Lock (GIL) per garantire che sia possibile programmare solo un thread Ruby alla volta.

    Come la risonanza magnetica, i thread C possono effettivamente funzionare parallelamente ai thread Ruby.

    In futuro, è possibile che il GIL possa essere suddiviso in blocchi più dettagliati, consentendo in tal modo un numero sempre maggiore di codice effettivamente eseguito in parallelo, ma è così lontano che non è nemmeno ancora pianificato .

  3. JRuby implementa i thread rubini come thread nativi , dove "thread nativi" nel caso della JVM significa ovviamente "thread JVM". JRuby non impone alcun blocco aggiuntivo su di essi. Quindi, se questi thread possono effettivamente essere eseguiti in parallelo dipende dalla JVM: alcuni JVM implementano i thread JVM come thread del sistema operativo e altri come thread verdi. (Le JVM tradizionali di Sun / Oracle utilizzano esclusivamente thread del sistema operativo dal JDK 1.3)

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

  5. IronRuby implementa i thread rubini come thread nativi , dove "thread nativi" nel caso del CLR significa ovviamente "thread CLR". IronRuby non impone alcun blocco aggiuntivo su di essi, quindi, dovrebbero funzionare in parallelo, purché il CLR lo supporti.

  6. Ruby.NET implementa anche i thread Ruby come thread CLR . Aggiornamento: Ruby.NET è morto.

  7. Rubinius implementa i thread di Ruby come thread verdi all'interno della sua macchina virtuale . Più precisamente: la VM Rubinius esporta un costrutto molto leggero e molto flessibile di concorrenza / parallelismo / flusso di controllo non locale, chiamato " Task ", e tutti gli altri costrutti di concorrenza (Discussioni in questa discussione, ma anche Continuations , Actors e altre cose ) sono implementati in puro Ruby, usando Task.

    Rubinius non può (attualmente) pianificare i thread in parallelo, tuttavia, aggiungendo che non è un grosso problema: Rubinius può già eseguire più 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 oggetto 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. È già implementato per gli attori Rubinius .)

    Aggiornamento : le informazioni su Rubinius in questa risposta riguardano la VM Shotgun, che non esiste più. La "nuova" macchina virtuale C ++ non utilizza thread verdi pianificati su più macchine virtuali (ad esempio stile Erlang / BEAM), utilizza una macchina virtuale singola più tradizionale con più modelli di thread del sistema operativo nativo, proprio come quella utilizzata, ad esempio, dal CLR, Mono e praticamente ogni JVM.

  8. MacRuby è nato come porta di YARV in aggiunta a Objective-C Runtime e CoreFoundation e Cocoa Frameworks. Ora è notevolmente divergente da YARV, ma AFAIK attualmente condivide ancora lo stesso modello di threading con YARV . Aggiornamento: MacRuby dipende dal Garbage Collector delle mele che viene dichiarato obsoleto e verrà rimosso nelle versioni successive di MacOSX, MacRuby è non morto.

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

  10. MagLev è un'implementazione Ruby per la VM Gemtone / S Smalltalk . Non ho informazioni sul modello di threading utilizzato da GemStone / S, sul modello di threading utilizzato da MagLev 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 saranno in grado di funzionare in parallelo, perché JavaScript non supporta il vero parallelismo. Esiste una versione ActionScript di HotRuby, tuttavia, 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 che sia una cattiva scelta: 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 Processmodulo con il Process.fork metodo che semplifica notevolmente il fork di un altro processo Ruby. Inoltre, la Ruby Standard Library contiene la libreria Distributed Ruby (dRuby / dRb) , che consente al codice Ruby di essere distribuito banalmente su più processi, non solo sulla stessa macchina ma anche attraverso la rete.


1
ma l'utilizzo di fork interromperà l'utilizzo su jruby ... solo dicendo
akostadinov il

1
Questa è un'ottima risposta Tuttavia è soggetto a un sacco di marciume di collegamento. Non so dove queste risorse potrebbero essersi spostate però.
BlackVegetable

28

Ruby 1.8 ha solo thread verdi, non c'è modo di creare un thread "a livello di sistema operativo" reale. Ma ruby ​​1.9 avrà una nuova funzionalità chiamata fibre, che ti permetterà di creare thread a livello di sistema operativo. Sfortunatamente, Ruby 1.9 è ancora in beta, è programmato per essere stabile tra un paio di mesi.

Un'altra alternativa è utilizzare JRuby. JRuby implementa i thread come thead a livello di sistema operativo, non ci sono "thread verdi" in esso. L'ultima versione di JRuby è la 1.1.4 ed è equivalente a Ruby 1.8


35
È falso che Ruby 1.8 abbia solo thread verdi, diverse implementazioni di Ruby 1.8 hanno thread nativi: JRuby, XRuby, Ruby.NET e IronRuby. Le fibre non consentono la creazione di thread nativi, sono più leggere dei thread. In realtà sono semi-coroutine, cioè sono cooperative.
Jörg W Mittag,

19
Penso che sia abbastanza ovvio dalla risposta di Josh che intende Ruby 1.8 il tempo di esecuzione, aka MRI, e non Ruby 1.8 la lingua, quando dice Ruby 1.8.
Theo,

@Theo È anche ovvio che confonde i concetti nella sua risposta. Le fibre non sono un modo per creare fili nativi, come già accennato, sono anche cose più leggere dei fili e l'attuale cruby ha fili nativi ma con GIL.
Foo Bar Zoo,

8

Dipende dall'implementazione:

  • La risonanza magnetica non ha, YARV è più vicino.
  • JRuby e MacRuby hanno.




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

Si noti che, essere in grado di creare veri thread "a livello di sistema operativo" non implica che è possibile utilizzare più core CPU per l'elaborazione parallela. Guarda gli esempi seguenti.

Questo è l'output di un semplice programma Ruby che utilizza 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 Rè in esecuzione solo quello con stato . 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. Esistono anche tre thread in esecuzione in parallelo. Questo perché i thread di MacRuby sono thread POSIX ( thread "a livello di sistema operativo" reali ) e non esiste 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 la buona vecchia risonanza magnetica. 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 sei interessato al multi-threading di Ruby potresti trovare interessante il mio rapporto Debugging dei programmi paralleli che usano i gestori fork .
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 Ruby non funzionano in parallelo.


Per RMI, intendi la RM?
Mayuresh Srivastava,

4

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


3

Lascerò che il "Monitor di sistema" risponda a questa domanda. Sto eseguendo lo stesso codice (sotto, che calcola i numeri primi) con 8 thread Ruby in esecuzione 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) (03-02-2014 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 per il completamento è leggermente più breve per il Ruby interpretato. È un po 'difficile distinguerlo dal grafico, ma la seconda esecuzione (interpretata da Ruby) utilizza circa 1/2 delle CPU (no hyperthreading?)

inserisci qui la descrizione dell'immagine

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

Se si utilizza la risonanza magnetica, è possibile scrivere il codice threaded in C come estensione o utilizzando la gemma in linea ruby.


1

Se hai davvero bisogno di parallelismo in Ruby per un sistema a livello di produzione (dove non puoi impiegare una beta) i processi sono probabilmente un'alternativa migliore.
Ma vale sicuramente la pena provare prima i thread con JRuby.

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


JRuby è una buona opzione. Per l'elaborazione parallela mediante processi mi piace github.com/grosser/parallel Parallel.map(['a','b','c'], :in_processes=>3){...
user454322


1

Perché non è possibile modificare quella risposta, quindi aggiungi una nuova risposta qui.

Aggiornamento (2017/05/08)

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

  1. Opal è un compilatore da sorgente a sorgente da Ruby a JavaScript. Ha anche un'implementazione del corelib di Ruby, attualmente molto attivo, ed esiste una grande quantità di framework (frontend) su cui ha lavorato. e produzione pronta. Perché basato su JavaScript, non supporta 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 che contiene anche il codice dell'implementazione standard di Ruby, MRI, sviluppo ancora attivo, non pronto per la produzione. Questa versione ruby ​​sembra nata per le prestazioni, non so se supportare i thread paralleli, ma penso che dovrebbe.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.