Le rubis a-t-il un véritable multithreading?

295

Je connais le filetage "coopératif" du rubis à l'aide de fils verts . Comment puis-je créer de vrais threads "au niveau du système d'exploitation" dans mon application afin d'utiliser plusieurs cœurs de processeur pour le traitement?

skolima
la source

Réponses:

612

Mis à jour avec le commentaire de Jörg de septembre 2011

Vous semblez confondre deux choses très différentes ici: le langage de programmation Ruby et le modèle de thread spécifique d'une implémentation spécifique du langage de programmation Ruby. Il existe actuellement environ 11 implémentations différentes du langage de programmation Ruby, avec des modèles de threads très différents et uniques.

(Malheureusement, seulement deux de ces 11 implémentations sont réellement prêtes pour une utilisation en production, mais d'ici la fin de l'année, ce nombre passera probablement à quatre ou cinq.) ( Mise à jour : il est maintenant 5: MRI, JRuby, YARV (l'interprète pour Ruby 1.9), Rubinius et IronRuby).

  1. La première implémentation n'a en fait pas de nom, ce qui rend assez maladroit de s'y référer et est vraiment ennuyeux et déroutant. Il est le plus souvent appelé "Ruby", ce qui est encore plus ennuyeux et déroutant que de ne pas avoir de nom, car cela conduit à une confusion sans fin entre les fonctionnalités du langage de programmation Ruby et une implémentation Ruby particulière.

    Il est aussi parfois appelé "IRM" (pour "Matz's Ruby Implementation"), CRuby ou MatzRuby.

    L'IRM implémente les fils Ruby en tant que fils verts dans son interpréteur . Malheureusement, cela ne permet pas de planifier ces threads en parallèle, ils ne peuvent exécuter qu'un seul thread à la fois.

    Cependant, un nombre illimité de threads C (threads POSIX, etc.) peuvent s'exécuter en parallèle avec le thread Ruby, de sorte que les bibliothèques C externes ou les extensions C IRM qui créent leurs propres threads peuvent toujours s'exécuter en parallèle.

  2. La deuxième implémentation est YARV (abréviation de "Yet Another Ruby VM"). YARV implémente les threads Ruby en tant que threads POSIX ou Windows NT , cependant, il utilise un verrou d'interpréteur global (GIL) pour garantir qu'un seul thread Ruby peut réellement être planifié à la fois.

    Comme l'IRM, les threads C peuvent en fait fonctionner parallèlement aux threads Ruby.

    À l'avenir, il est possible que le GIL pourrait se décomposer en plus serrures à grains fins, ce qui permet ainsi de plus en plus de code pour réellement fonctionner en parallèle, mais qui est si loin, il est même pas prévu encore.

  3. JRuby implémente Ruby Threads en tant que threads natifs , où "Native Threads" dans le cas de la JVM signifie évidemment "JVM Threads". JRuby ne leur impose aucun verrouillage supplémentaire. Donc, si ces threads peuvent réellement s'exécuter en parallèle dépend de la JVM: certaines JVM implémentent des threads JVM en tant que threads OS et d'autres en tant que threads verts. (Les machines virtuelles Java classiques de Sun / Oracle utilisent exclusivement des threads de système d'exploitation depuis JDK 1.3)

  4. XRuby implémente également des threads Ruby en tant que threads JVM . Mise à jour : XRuby est mort.

  5. IronRuby implémente Ruby Threads en tant que threads natifs , où "Native Threads" dans le cas du CLR signifie évidemment "CLR Threads". IronRuby ne leur impose aucun verrouillage supplémentaire, ils devraient donc fonctionner en parallèle, tant que votre CLR le prend en charge.

  6. Ruby.NET implémente également les threads Ruby en tant que threads CLR . Mise à jour: Ruby.NET est mort.

  7. Rubinius implémente les fils Ruby en tant que fils verts dans sa machine virtuelle . Plus précisément: la machine virtuelle Rubinius exporte une construction concurrentielle / parallélisme / flux de contrôle non local très légère, très flexible, appelée « tâche », et toutes les autres constructions simultanées (discussions dans cette discussion, mais aussi Continuations , acteurs et autres) ) sont implémentées en Ruby pur, en utilisant des tâches.

    Rubinius ne peut pas (actuellement) planifier des threads en parallèle, cependant, ce qui n'est pas trop un problème: Rubinius peut déjà exécuter plusieurs instances de VM dans plusieurs threads POSIX en parallèle , au sein d'un processus Rubinius. Étant donné que les threads sont réellement implémentés dans Ruby, ils peuvent, comme tout autre objet Ruby, être sérialisés et envoyés à une machine virtuelle différente dans un thread POSIX différent. (C'est le même modèle que la machine virtuelle BEAM Erlang utilise pour la concurrence SMP. Il est déjà implémenté pour les acteurs Rubinius .)

    Mise à jour : Les informations sur Rubinius dans cette réponse concernent la machine virtuelle Shotgun, qui n'existe plus. La "nouvelle" machine virtuelle C ++ n'utilise pas de threads verts planifiés sur plusieurs machines virtuelles (c'est-à-dire le style Erlang / BEAM), elle utilise une machine virtuelle unique plus traditionnelle avec plusieurs modèles de threads natifs du système d'exploitation, tout comme celui employé, disons, par le CLR, Mono et à peu près toutes les machines virtuelles Java.

  8. MacRuby a commencé comme port de YARV en plus des cadres d'exécution Objective-C et CoreFoundation et Cocoa. Il a maintenant considérablement divergé de YARV, mais AFAIK partage actuellement le même modèle de filetage avec YARV . Mise à jour: MacRuby dépend du ramasse-miettes de pommes qui est déclaré obsolète et sera supprimé dans les versions ultérieures de MacOSX, MacRuby est mort-vivant.

  9. Cardinal est une implémentation Ruby pour la machine virtuelle Parrot . Il n'implémente pas encore de threads, cependant, quand il le fera, il les implémentera probablement en tant que threads Parrot . Mise à jour : le cardinal semble très inactif / mort.

  10. Maglev est une implémentation Ruby pour le Smalltalk GemStone / S VM . Je n'ai aucune information sur le modèle de thread que GemStone / S utilise, sur le modèle de thread que MagLev utilise ou même si les threads sont encore implémentés (probablement pas).

  11. HotRuby n'est pas une implémentation complète de Ruby. Il s'agit d'une implémentation d'une machine virtuelle bytecode YARV en JavaScript. HotRuby ne prend pas (encore?) En charge les threads et, dans ce cas, ils ne pourront pas s'exécuter en parallèle, car JavaScript ne prend pas en charge le vrai parallélisme. Il existe cependant une version ActionScript de HotRuby, et ActionScript peut en fait prendre en charge le parallélisme. Mise à jour : HotRuby est mort.

Malheureusement, seules deux de ces 11 implémentations Ruby sont réellement prêtes pour la production: l'IRM et JRuby.

Donc, si vous voulez de vrais threads parallèles, JRuby est actuellement votre seul choix - pas que ce soit un mauvais choix: JRuby est en fait plus rapide que l'IRM, et sans doute plus stable.

Sinon, la solution Ruby "classique" consiste à utiliser des processus plutôt que des threads pour le parallélisme. La bibliothèque Ruby Core contient le Processmodule avec la Process.fork méthode qui le rend très facile à débuter un autre processus Ruby. En outre, la bibliothèque standard Ruby contient la bibliothèque Ruby distribué (dRuby / dRb) , qui permet au code Ruby d'être distribué de manière triviale sur plusieurs processus, non seulement sur la même machine mais également sur le réseau.

Jörg W Mittag
la source
1
mais utiliser fork va casser l'utilisation sur jruby ... juste dire
akostadinov
1
C'est une excellente réponse. Cependant, il est soumis à beaucoup de pourriture des liens. Je ne sais pas où ces ressources ont pu se déplacer cependant.
BlackVegetable
28

Ruby 1.8 n'a que des threads verts, il n'y a aucun moyen de créer un vrai thread "au niveau du système d'exploitation". Mais, ruby ​​1.9 aura une nouvelle fonctionnalité appelée fibres, qui vous permettra de créer de véritables threads au niveau du système d'exploitation. Malheureusement, Ruby 1.9 est toujours en version bêta, il devrait être stable dans quelques mois.

Une autre alternative consiste à utiliser JRuby. JRuby implémente les threads en tant que threads de niveau OS, il n'y a pas de "threads verts" dedans. La dernière version de JRuby est 1.1.4 et est équivalente à Ruby 1.8

Josh Moore
la source
35
Il est faux que Ruby 1.8 n'ait que des threads verts, plusieurs implémentations de Ruby 1.8 ont des threads natifs: JRuby, XRuby, Ruby.NET et IronRuby. Les fibres ne permettent pas la création de threads natifs, ils sont plus légers que les threads. Ce sont en fait des semi-coroutines, c'est-à-dire qu'elles sont coopératives.
Jörg W Mittag
19
Je pense qu'il est assez évident d'après la réponse de Josh qu'il veut dire Ruby 1.8 le runtime, alias MRI, et non Ruby 1.8 le langage, quand il dit Ruby 1.8.
Theo
@Theo Il est également évident qu'il gâche des concepts dans sa réponse. Les fibres ne sont pas un moyen de créer des threads natifs, comme déjà mentionné, ce sont des choses encore plus légères que les threads et le cruby actuel a des threads natifs mais avec GIL.
Foo Bar Zoo
8

Cela dépend de la mise en œuvre:

  • L'IRM n'a pas, YARV est plus proche.
  • JRuby et MacRuby l'ont fait.




Ruby a des fermetures comme Blocks, lambdaset Procs. Pour tirer pleinement parti des fermetures et des cœurs multiples dans JRuby, les exécuteurs Java sont utiles; pour MacRuby, j'aime les files d'attente de GCD .

Notez que, la possibilité de créer de véritables threads "au niveau du système d'exploitation" n'implique pas que vous pouvez utiliser plusieurs cœurs de processeur pour le traitement parallèle. Regardez les exemples ci-dessous.

Voici la sortie d' un simple programme Ruby qui utilise 3 threads utilisant 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 

Comme vous pouvez le voir ici, il existe quatre threads de système d'exploitation, mais seul celui avec état Rest en cours d'exécution. Cela est dû à une limitation dans la façon dont les threads de Ruby sont implémentés.



Même programme, maintenant avec JRuby. Vous pouvez voir trois threads avec état R, ce qui signifie qu'ils s'exécutent en parallèle.

(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 


Le même programme, maintenant avec MacRuby. Il existe également trois threads exécutés en parallèle. En effet, les threads MacRuby sont des threads POSIX ( vrais threads "au niveau du système d'exploitation" ) et il n'y a pas de 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 


Encore une fois, le même programme mais maintenant avec la bonne vieille IRM. Étant donné que cette implémentation utilise des threads verts, un seul thread apparaît

(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



Si vous êtes intéressé par le multithread Ruby, vous trouverez peut-être mon rapport Débogage de programmes parallèles utilisant des gestionnaires de fourches intéressant.
Pour un aperçu plus général des composants internes de Ruby, Ruby Under a Microscope est une bonne lecture.
En outre, Ruby Threads et le Global Interpreter Lock en C dans Omniref explique dans le code source pourquoi les threads Ruby ne s'exécutent pas en parallèle.

user454322
la source
Par RMI, vous voulez dire l'IRM?
Mayuresh Srivastava
4

Que diriez-vous d'utiliser drb ? Ce n'est pas un vrai multi-threading mais une communication entre plusieurs processus, mais vous pouvez l'utiliser maintenant en 1.8 et sa friction est assez faible.

ujh
la source
3

Je vais laisser le "Moniteur système" répondre à cette question. J'exécute le même code (ci-dessous, qui calcule les nombres premiers) avec 8 threads Ruby exécutés sur une machine i7 (4 hyperthreaded-core) dans les deux cas ... la première exécution est avec:

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]

Le second est avec:

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

Fait intéressant, le CPU est plus élevé pour les threads JRuby, mais le temps de réalisation est légèrement plus court pour le Ruby interprété. C'est un peu difficile à dire à partir du graphique, mais la deuxième exécution (Ruby interprété) utilise environ la moitié des processeurs (pas d'hyperthreading?)

entrez la description de l'image ici

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
GroovyCakes
la source
1

Si vous utilisez l'IRM, vous pouvez écrire le code fileté en C soit en tant qu'extension, soit en utilisant la gemme ruby-inline.


la source
1

Si vous avez vraiment besoin du parallélisme dans Ruby pour un système de niveau de production (où vous ne pouvez pas utiliser une version bêta), les processus sont probablement une meilleure alternative.
Mais, cela vaut certainement la peine d'essayer les threads sous JRuby en premier.

De plus, si vous êtes intéressé par l'avenir du filetage sous Ruby, cet article pourrait vous être utile.

Pascal
la source
JRuby est une bonne option. Pour le traitement parallèle à l'aide de processus, j'aime github.com/grosser/parallel Parallel.map(['a','b','c'], :in_processes=>3){...
user454322
1

Impossible de modifier cette réponse. Ajoutez une nouvelle réponse ici.

Mise à jour (2017-05-08)

Cet article est très ancien et les informations ne suivent pas la bande de roulement actuelle (2017), voici un supplément:

  1. Opal est un compilateur source-à-source Ruby vers JavaScript. Il a également une implémentation du corelib Ruby, Il est actuellement très actif en développement, et il existe beaucoup de framework (frontend) travaillé dessus. et prêt pour la production. Parce que basé sur javascript, il ne prend pas en charge les threads parallèles.

  2. truffleruby est une implémentation hautes performances du langage de programmation Ruby. Construit sur le GraalVM par Oracle Labs, TruffleRuby est un fork de JRuby, le combinant avec du code du projet Rubinius, et contenant également du code de l'implémentation standard de Ruby, MRI, toujours en développement, pas prêt pour la production. Cette version ruby ​​semble née pour la performance, je ne sais pas si elle prend en charge les threads parallèles, mais je pense que cela devrait.

zw963
la source