O ruby ​​tem multithreading real?

Eu sei sobre o threading “cooperativo” de ruby usando fios verdes . Como posso criar encadeamentos “no nível do sistema operacional” reais em meu aplicativo para usar vários núcleos de CPU para processamento?

Atualizado com o comentário de Jörg em setembro de 2011

Você parece estar confundindo duas coisas muito diferentes aqui: a Linguagem de Programação Ruby e o modelo de encadeamento específico de uma implementação específica da Linguagem de Programação Ruby. Existem atualmente cerca de 11 implementações diferentes da Linguagem de Programação Ruby, com modelos de encadeamento muito diferentes e únicos.

(Infelizmente, apenas duas dessas 11 implementações estão realmente prontas para uso em produção, mas até o final do ano esse número provavelmente subirá para quatro ou cinco.) ( Atualização : agora são 5: MRI, JRuby, YARV para Ruby 1.9), Rubinius e IronRuby).

  1. A primeira implementação não tem realmente um nome, o que torna bastante complicado referenciá-lo e é realmente irritante e confuso. É mais comumente chamado de “Ruby”, o que é ainda mais irritante e confuso do que não ter nome, porque leva a uma confusão infinita entre os resources da Linguagem de Programação Ruby e uma Implementação Ruby específica.

    É também chamado de “MRI” (para “Matz’s Ruby Implementation”), CRuby ou MatzRuby.

    MRI implementa Ruby Threads como Green Threads em seu interpretador . Infelizmente, ele não permite que esses threads sejam agendados em paralelo, eles só podem executar um thread de cada vez.

    No entanto, qualquer número de Threads C (Threads POSIX, etc.) pode ser executado em paralelo ao Ruby Thread, portanto, as bibliotecas C externas ou MRI C Extensions que criam threads próprias ainda podem ser executadas em paralelo.

  2. A segunda implementação é YARV (abreviação de “Yet Another Ruby VM”). O YARV implementa Ruby Threads como POSIX ou Windows NT Threads , no entanto, ele usa um Global Interpreter Lock (GIL) para garantir que apenas um Ruby Thread possa realmente ser agendado a qualquer momento.

    Como o MRI, os C Threads podem ser executados paralelamente aos Ruby Threads.

    No futuro, é possível que o GIL seja dividido em bloqueios mais refinados, permitindo que mais e mais códigos sejam executados em paralelo, mas isso está muito longe, ainda não está planejado .

  3. O JRuby implementa Ruby Threads como Threads Nativo , onde “Threads Nativos” no caso da JVM obviamente significa “Threads da JVM”. O JRuby não impõe nenhum bloqueio adicional a eles. Portanto, se esses encadeamentos podem, na verdade, ser executados em paralelo, depende da JVM: algumas JVMs implementam encadeamentos da JVM como encadeamentos do SO e outros como Encadeamentos Verdes. (As principais JVMs da Sun / Oracle usam exclusivamente threads do sistema operacional desde o JDK 1.3)

  4. O XRuby também implementa Ruby Threads como Threads da JVM . Atualização : XRuby está morto.

  5. IronRuby implementa Ruby Threads como Threads Nativo , onde “Threads Nativo” no caso do CLR obviamente significa “Threads do CLR”. IronRuby não impõe nenhum bloqueio adicional a eles, então, eles devem rodar em paralelo, contanto que seu CLR suporte isso.

  6. O Ruby.NET também implementa Ruby Threads como Threads do CLR . Atualização: Ruby.NET está morto.

  7. O Rubinius implementa Ruby Threads como Threads Verdes dentro de sua Máquina Virtual . Mais precisamente: a VM Rubinius exporta uma construção de stream de controle muito leve, muito flexível de simultaneidade / paralelismo / não local, chamada de ” Tarefa ” e todas as outras construções de simultaneidade (Threads nesta discussão, mas também Continuations , Atores e outras coisas ) são implementados em puro Ruby, usando Tasks.

    Rubinius não pode (atualmente) agendar Threads em paralelo, no entanto, acrescentando que não é um problema muito grande: Rubinius já pode executar várias instâncias de VMs em vários Threads POSIX em paralelo , dentro de um processo Rubinius. Como os Threads são realmente implementados em Ruby, eles podem, como qualquer outro object Ruby, ser serializados e enviados para uma VM diferente em um Thread POSIX diferente. (Esse é o mesmo modelo que o BEAM Erlang VM usa para a simultaneidade do SMP. Ele já está implementado para o Rubinius Actors .)

    Atualização : As informações sobre o Rubinius nesta resposta são sobre a VM Shotgun, que não existe mais. A “nova” VM C ++ não usa threads verdes programados em várias VMs (ou seja, estilo Erlang / BEAM), usa uma VM mais tradicional com vários modelos de threads do sistema operacional nativo, exatamente como o empregado pelo, digamos, CLR e praticamente todas as JVMs.

  8. MacRuby começou como uma porta do YARV no topo do Objective-C Runtime e CoreFoundation e Cocoa Frameworks. Agora divergiu significativamente do YARV, mas o AFAIK atualmente ainda compartilha o mesmo Modelo de Threading com o YARV . Atualização: MacRuby depende do coletor de lixo das maçãs, que é declarado obsoleto e será removido em versões posteriores do MacOSX, MacRuby é morto-vivo.

  9. Cardinal é uma implementação Ruby para a Máquina Virtual Parrot . Ele não implementa threads ainda, no entanto, quando isso acontece, provavelmente irá implementá-los como Parrot Threads . Atualização : Cardeal parece muito inativo / morto.

  10. MagLev é uma implementação Ruby para a VM GemStone / S Smalltalk . Eu não tenho nenhuma informação sobre qual modelo de threading GemStone / S usa, qual modelo de threading MagLev usa ou até mesmo se threads ainda estão implementados (provavelmente não).

  11. O HotRuby não é uma implementação completa do Ruby. É uma implementação de uma VM de bytecode YARV em JavaScript. HotRuby não suporta threads (ainda?) E quando isso acontece, eles não poderão rodar em paralelo, porque o JavaScript não tem suporte para o verdadeiro paralelismo. No entanto, existe uma versão do ActionScript do HotRuby, e o ActionScript pode realmente oferecer suporte ao paralelismo. Atualização : HotRuby está morto.

Infelizmente, apenas duas dessas 11 implementações de Ruby estão realmente prontas para produção: MRI e JRuby.

Então, se você quer verdadeiras threads paralelas, o JRuby é atualmente sua única escolha – não que seja ruim: JRuby é na verdade mais rápido que MRI, e sem dúvida mais estável.

Caso contrário, a solução Ruby “clássica” é usar processos em vez de threads para paralelismo. A Ruby Core Library contém o módulo Process com o método Process.fork , o que torna fácil extrair outro processo Ruby. Além disso, a biblioteca padrão do Ruby contém a biblioteca Distributed Ruby (dRuby / dRb) , que permite que o código Ruby seja distribuído trivialmente em vários processos, não apenas na mesma máquina, mas também na rede.

O Ruby 1.8 tem apenas encadeamentos verdes, não há como criar um encadeamento real no nível do sistema operacional. Mas, Ruby 1.9 terá um novo recurso chamado fibras, que permitirá que você crie segmentos reais no nível do sistema operacional. Infelizmente, o Ruby 1.9 ainda está em beta, está programado para ser estável em alguns meses.

Outra alternativa é usar o JRuby. O JRuby implementa threads como theads no nível do SO, não há “encadeamentos verdes” nele. A última versão do JRuby é 1.1.4 e equivale a Ruby 1.8

Depende da implementação:

  • RMI não tem, YARV está mais perto.
  • JRuby e MacRuby têm.

Ruby tem encerramentos como Blocks , lambdas e Procs . Para aproveitar ao máximo os fechamentos e múltiplos núcleos no JRuby, os executores do Java são úteis; para MacRuby Eu gosto das filas do GCD .

Observe que, a capacidade de criar encadeamentos reais de “nível do sistema operacional” não significa que você pode usar vários núcleos de CPU para parallel processing. Veja os exemplos abaixo.

Esta é a saída de um programa Ruby simples que usa 3 threads 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/rubyes/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 

Como você pode ver aqui, existem quatro threads do sistema operacional, no entanto, apenas aquele com o estado R está sendo executado. Isto é devido a uma limitação na forma como os threads do Ruby são implementados.


Mesmo programa, agora com o JRuby. Você pode ver três encadeamentos com o estado R , o que significa que eles estão sendo executados em paralelo.

 (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/rubyes/jruby-1.7.10 -Djruby.script=jruby -Djruby.shell=/bin/sh -Djffi.boot.library.path=/Users/jalcazar/.rvm/rubyes/jruby-1.7.10/lib/jni:/Users/jalcazar/.rvm/rubyes/jruby-1.7.10/lib/jni/Darwin -Xss2048k -Dsun.java.command=org.jruby.Main -cp -Xbootclasspath/a:/Users/jalcazar/.rvm/rubyes/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 

O mesmo programa, agora com o MacRuby. Existem também três threads em execução em paralelo. Isso ocorre porque os encadeamentos MacRuby são encadeamentos POSIX ( encadeamentos “no nível do sistema operacional” reais ) e não há 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/rubyes/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 

Mais uma vez, o mesmo programa, mas agora com o bom e velho RMI. Devido ao fato de que essa implementação usa encadeamentos verdes, apenas um encadeamento aparece

 (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/rubyes/ruby-1.8.7-p374/bin/ruby threads.rb 

Se você estiver interessado em multi-threading Ruby você pode encontrar meu relatório Depurando programas paralelos usando manipuladores de garfo interessante.
Para uma visão geral mais geral dos componentes internos do Ruby, Ruby Under a Microscope é uma boa leitura.
Além disso, Ruby Threads e o Global Interpreter Lock em C no Omniref explicam no código-fonte por que os encadeamentos Ruby não são executados em paralelo.

Como cerca de usar drb ? Não é multi-threading real, mas a comunicação entre vários processos, mas você pode usá-lo agora em 1,8 e é bastante baixo atrito.

Vou deixar o “System Monitor” responder a esta pergunta. Estou executando o mesmo código (abaixo, que calcula números primos) com 8 encadeamentos Ruby em execução em uma máquina i7 (4 hyperthreaded-core) em ambos os casos … a primeira execução é com:

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

O segundo é com:

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

Curiosamente, a CPU é maior para os encadeamentos JRuby, mas o tempo para conclusão é um pouco menor para o Ruby interpretado. É meio difícil dizer a partir do gráfico, mas a segunda execução (interpretada Ruby) usa cerca de metade das CPUs (sem hyperthreading?)

insira a descrição da imagem aqui

 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 você estiver usando ressonância magnética, então você pode escrever o código encadeado em C como uma extensão ou usando a gem inline ruby.

Se você realmente precisa de paralelismo em Ruby para um sistema de nível de produção (onde você não pode empregar um beta), os processos provavelmente são uma alternativa melhor.
Mas, definitivamente vale a pena tentar threads sob JRuby primeiro.

Além disso, se você estiver interessado no futuro da segmentação em Ruby, poderá achar este artigo útil.

Aqui estão algumas informações sobre Rinda, que é a implementação Ruby de Linda (parallel processing e paradigma de computação distribuída) http://charmalloc.blogspot.com/2009/12/linda-tuples-rinda-drb-parallel.html

Como não foi possível editar essa resposta, adicione uma nova resposta aqui.

Atualização (2017-05-08)

Este artigo é muito antigo, e as informações não seguem os passos atuais (2017), A seguir está um suplemento:

  1. O Opal é um compilador Ruby to JavaScript source-to-source. Ele também tem uma implementação do Ruby corelib, ele é muito ativo, e existe uma grande parte do framework (frontend) trabalhado nele. e produção pronta. Porque base em javascript, não suporta threads paralelos.

  2. truffleruby é uma implementação de alto desempenho da linguagem de programação Ruby. Construído sobre o GraalVM pelo Oracle Labs, o TruffleRuby é um fork do JRuby, combinando-o com o código do projeto Rubinius, e também contendo código da implementação padrão do Ruby, MRI, desenvolvimento ainda ativo, não pronto para produção. Esta versão ruby ​​parece ter nascido para performance, não sei se suporto threads paralelos, mas acho que deveria.