Ruby tem multithreading real?

295

Eu sei sobre a segmentação "cooperativa" de rubi usando linhas verdes . Como posso criar threads "no nível do sistema operacional" reais no meu aplicativo para fazer uso de vários núcleos de CPU no processamento?

Skolima
fonte

Respostas:

612

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

Você parece confundir 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. Atualmente, existem 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 chegará a quatro ou cinco.) ( Atualização : agora são 5: MRI, JRuby, YARV (o intérprete para Ruby 1.9), Rubinius e IronRuby).

  1. A primeira implementação, na verdade, não tem um nome, o que torna bastante estranho se referir a ela e é realmente irritante e confuso. É frequentemente chamado de "Ruby", que é ainda mais irritante e confuso do que não ter nome, porque leva a uma confusão infinita entre os recursos da Ruby Programming Language e uma implementação específica de Ruby.

    Às vezes, também é chamado de "MRI" (para "Implementação do Ruby em Matz"), CRuby ou MatzRuby.

    A MRI implementa Ruby Threads como Green Threads em seu intérprete . Infelizmente, ele não permite que esses threads sejam agendados em paralelo, eles podem executar apenas um thread por vez.

    No entanto, qualquer número de threads C (threads POSIX etc.) pode ser executado em paralelo com o Ruby Thread; portanto, as bibliotecas C externas ou as extensões C da MRI que criam seus próprios segmentos 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 , no entanto, ele usa um Global Interpreter Lock (GIL) para garantir que apenas um Ruby Thread possa realmente ser agendado a qualquer momento.

    Como a ressonância magnética, os C Threads podem ser executados paralelamente aos Ruby Threads.

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

  3. O JRuby implementa Threads Ruby como Threads Nativos , 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 realmente ser executados em paralelo depende da JVM: algumas JVMs implementam encadeamentos da JVM como encadeamentos do SO e outras como encadeamentos verdes. (As JVMs convencionais da Sun / Oracle usam exclusivamente threads do SO desde o JDK 1.3)

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

  5. O IronRuby implementa Threads Ruby como Threads Nativos , onde "Threads Nativos" no caso do CLR obviamente significa "Threads CLR". O IronRuby não impõe nenhum bloqueio adicional a eles, portanto, eles devem ser executados em paralelo, desde que o seu CLR suporte isso.

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

  7. O Rubinius implementa os Ruby Threads como Green Threads em sua Máquina Virtual . Mais precisamente: a Rubinius VM exporta uma construção de fluxo de controle de concorrência / paralelismo / não local muito leve e flexível, chamada de " Tarefa " e todas as outras construções de simultaneidade (tópicos nesta discussão, mas também continuações , atores e outras coisas) ) são implementados em Ruby puro, usando Tarefas.

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

    Atualização : As informações sobre Rubinius nesta resposta são sobre a Shotgun VM, que não existe mais. A "nova" VM C ++ não usa threads verdes agendados em várias VMs (estilo Erlang / BEAM), usa uma VM única mais tradicional com vários modelos de threads de SO nativos, exatamente como o empregado por, por exemplo, o CLR, Mono e praticamente todas as JVM.

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

  9. Cardinal é uma implementação Ruby para a máquina virtual Parrot . Ele ainda não implementa threads, no entanto, quando o fizer, provavelmente os implementará como Parrot Threads . Atualização : O cardeal parece muito inativo / morto.

  10. MagLev é uma implementação Ruby para a VM GemStone / S Smalltalk . Não tenho informações sobre o modelo de encadeamento GemStone / S, qual modelo de encadeamento o MagLev usa ou mesmo se os threads já foram 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. O HotRuby não suporta threads (ainda?) E, quando isso acontecer, eles não poderão executar em paralelo, porque o JavaScript não suporta o verdadeiro paralelismo. No entanto, existe uma versão do HotRuby do ActionScript, e o ActionScript pode realmente oferecer suporte ao paralelismo. Atualização : HotRuby está morto.

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

Portanto, se você quiser threads paralelos verdadeiros, o JRuby é atualmente a sua única opção - não que seja ruim: o JRuby é realmente mais rápido que o 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 Processmódulo com o Process.fork método que facilita a execução de outro processo Ruby. Além disso, a Ruby Standard Library contém a biblioteca Distributed Ruby (dRuby / dRb) , que permite que o código Ruby seja distribuído trivialmente por vários processos, não apenas na mesma máquina, mas também na rede.

Jörg W Mittag
fonte
1
mas usando garfo vai quebrar o uso em jruby ... apenas dizendo
akostadinov
1
Esta é uma ótima resposta. No entanto, está sujeito a muita podridão de link. Não sei para onde esses recursos podem ter se mudado.
precisa saber é o seguinte
28

O Ruby 1.8 possui apenas threads verdes, não há como criar um thread "no nível do sistema operacional" real. Mas, o ruby ​​1.9 terá um novo recurso chamado fibras, que permitirá criar threads reais no nível do sistema operacional. Infelizmente, o Ruby 1.9 ainda está na versão beta, está programado para ficar estável em alguns meses.

Outra alternativa é usar o JRuby. O JRuby implementa threads como threads no nível do SO; não há "threads verdes" nele. A versão mais recente do JRuby é 1.1.4 e é equivalente ao Ruby 1.8

Josh Moore
fonte
35
É falso que o Ruby 1.8 tenha apenas threads verdes, várias implementações do Ruby 1.8 têm threads nativas: JRuby, XRuby, Ruby.NET e IronRuby. As fibras não permitem a criação de threads nativos, são mais leves que os threads. Na verdade, são semi-corotinas, ou seja, são cooperativas.
Jörg W Mittag
19
Eu acho que é bastante óbvio pela resposta de Josh que ele quer dizer Ruby 1.8 no tempo de execução, também conhecido como MRI, e não Ruby 1.8 na linguagem, quando ele diz Ruby 1.8.
Theo
@ Theo Também é óbvio que ele estraga conceitos em sua resposta. As fibras não são uma maneira de criar threads nativos, como já mencionado, são coisas ainda mais leves que os threads e o cruby atual possui threads nativos, mas com o GIL.
Foo Bar Zoo
8

Depende da implementação:

  • Ressonância magnética não tem, YARV está mais perto.
  • JRuby e MacRuby têm.




Ruby tem fechamentos como Blocks, lambdase Procs. Para tirar o máximo proveito de fechamentos e múltiplos núcleos no JRuby, os executores de Java são úteis; para MacRuby, eu gosto das filas do GCD .

Observe que, sendo capaz de criar threads "no nível do sistema operacional" reais não implica que você possa usar vários núcleos de CPU para processamento paralelo. Veja os exemplos abaixo.

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

Como você pode ver aqui, existem quatro threads do sistema operacional, no entanto, apenas aquele com state Restá em execução. Isso ocorre devido a uma limitação na maneira como os threads do Ruby são implementados.



O mesmo programa, agora com o JRuby. Você pode ver três threads com state 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/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 


O mesmo programa, agora com MacRuby. Também há três threads em execução em paralelo. Isso ocorre porque os threads do MacRuby são threads do POSIX ( threads reais "no nível do SO" ) 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/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 


Mais uma vez, o mesmo programa, mas agora com a boa e velha ressonância magnética. Devido ao fato de esta implementação usar threads verdes, apenas um thread 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/rubies/ruby-1.8.7-p374/bin/ruby threads.rb



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

user454322
fonte
Por RMI, você quer dizer ressonância magnética?
Mayuresh Srivastava
4

Que tal usar drb ? Não é multi-threading real, mas comunicação entre vários processos, mas você pode usá-lo agora no 1.8 e é um atrito bastante baixo.

ujh
fonte
3

Vou deixar o "Monitor do Sistema" responder a esta pergunta. Estou executando o mesmo código (abaixo, que calcula números primos) com 8 threads Ruby em execução em uma máquina i7 (4 hyperthreaded-core) nos dois casos ... a primeira execução é com:

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

O segundo é com:

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

Curiosamente, a CPU é mais alta para threads do JRuby, mas o tempo para conclusão é um pouco menor para o Ruby interpretado. É meio difícil perceber o gráfico, mas a segunda execução (interpretada em 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
GroovyCakes
fonte
1

Se você estiver usando ressonância magnética, poderá escrever o código encadeado em C como uma extensão ou usando a gema ruby-inline.


fonte
1

Se você realmente precisa de paralelismo no 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 os threads no JRuby primeiro.

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

Pascal
fonte
JRuby é uma boa opção. Para processamento paralelo usando processos, eu gosto de github.com/grosser/parallel Parallel.map(['a','b','c'], :in_processes=>3){...
user454322
1

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

Atualização (08-05-2017)

Este artigo é muito antigo e as informações não seguem a trilha atual (2017). A seguir, é apresentado algum complemento:

  1. Opal é um compilador de origem a fonte de Ruby para JavaScript. Ele também possui uma implementação do Ruby corelib, atual desenvolvimento muito ativo e existe uma grande quantidade de framework (front-end) trabalhado nele. e produção pronta. Como base em javascript, ele não suporta threads paralelos.

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

zw963
fonte