Pergunta

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

Foi útil?

Solução

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

Você parece estar confundindo dois muito coisas diferentes aqui:o Linguagem de Programação Ruby e o modelo de threading específico de um implementação específica da Linguagem de Programação Ruby.Ali são atualmente cerca de 11 implementações diferentes do Ruby Linguagem de Programação, com muito rosqueamento diferente e único Modelos.

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

  1. A primeira implementação não tem um nome, que torna bastante estranho referir-se a ele e é realmente irritante e confuso.É mais frequentemente referido como "Ruby", que é mesmo mais chato e confuso do que não ter nome, porque leva a uma confusão sem fim entre as características do Ruby Linguagem de Programação e uma Implementação Ruby particular.

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

    MRI implementa Ruby Threads como Green Threads dentro de seu intérprete.Infelizmente, ele não permite esses tópicos para serem agendados em paralelo, eles só podem executar um thread em um Hora.

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

  2. A segunda implementação é YARV (abreviação de "Ainda Outra VM Ruby"). YARV implementa Ruby Threads como POSIX ou Windows NT Threads, no entanto, ele usa um intérprete global Bloquear (GIL) para garantir que apenas um Ruby Thread possa realmente ser agendado a qualquer momento.

    Como ressonância magnética, roscas C pode na verdade, são executados paralelamente ao Ruby Threads.

    No futuro, é possível que o GIL poder ficar quebrado para baixo em fechaduras de grão mais fino, permitindo assim mais e mais código para realmente executar em paralelo, mas isso está tão longe, é nem mesmo planejado ainda.

  3. JRuby implementa Ruby Threads como Native Threads, onde "Native Threads" no caso da JVM obviamente significa "JVM Fios".JRuby não impõe nenhum bloqueio adicional a eles.Então Se esses threads podem realmente ser executados em paralelo depende de a JVM:algumas JVMs implementam JVM Threads como OS Threads e algumas como Fios Verdes.(As principais JVMs da Sun/Oracle usam exclusivamente threads de sistema operacional desde o JDK 1.3)

  4. XRuby também implementa Ruby Threads como JVM Threads. Atualizar:XRuby está morto.

  5. FerroRubi implementa Ruby Threads como Native Threads, onde "Native Threads" no caso do CLR obviamente significa "Tópicos CLR".IronRuby não impõe nenhum bloqueio adicional sobre eles, portanto, eles devem ser executados em paralelo, desde que seu CLR suporte Isso.

  6. Ruby.NET também implementa Ruby Threads como CLR Tópicos. Atualizar: Ruby.NET está morto.

  7. Rubínio implementa Ruby Threads como Green Threads dentro de sua Máquina Virtual.Mais precisamente:o Rubínio VM exporta um muito leve, muito flexível contaneidade/paralelismo/construção de fluxo de controle não local, chamada a "Tarefa", e todas as outras construções de simultaneidade (Threads in essa discussão, mas também Continuações, Atores e outras coisas) são implementadas em Ruby puro, usando Tarefas.

    Rubinius não pode (atualmente) agendar Threads em paralelo, No entanto, adicionar isso não é um grande problema:Rubinius pode já executar várias instâncias de VM em vários threads POSIX em paralelo, dentro de um processo Rubinius.Uma vez que os Threads são realmente implementado em Ruby, eles podem, como qualquer outro Ruby objeto, ser serializado e enviado para uma VM diferente em um diferente Rosca POSIX.(Esse é o mesmo modelo que o BEAM Erlang VM usa para simultaneidade SMP.Já é implementado para Rubinius Atores.)

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

  8. MacRuby começou como um porto de YARV em cima do Objective-C Runtime e CoreFoundation e Cocoa Frameworks.Ela agora divergiu significativamente do YARV, mas AFAIK atualmente ainda compartilha o mesmo modelo de threading com YARV. Atualizar: MacRuby depende do coletor de lixo de maçãs que foi declarado obsoleto e será removido em versões posteriores do MacOSX, MacRuby é morto-vivo.

  9. Cardeal é uma implementação Ruby para o Papagaio Máquina virtual.Ele ainda não implementa threads, no entanto, quando o fizer, provavelmente irá implementá-los como Papagaio Tópicos. Atualizar:Cardinal parece muito inativo/morto.

  10. MagLev é uma implementação Ruby para o Pedra Preciosa/S Smalltalk VM.Não tenho informações sobre qual modelo de threading GemStone/S usa, qual modelo de rosqueamento MagLev usa ou mesmo se threads ainda estão implementados (provavelmente não).

  11. HotRuby é não uma implementação Ruby completa de sua possuir.É uma implementação de uma VM de bytecode YARV em JavaScript.HotRuby não suporta threads (ainda?) e quando ele Faz isso, eles não poderão ser executados em paralelo, porque o JavaScript não tem suporte para o verdadeiro paralelismo.Há um ActionScript versão do HotRuby, no entanto, e o ActionScript podem realmente suporte paralelismo. Atualizar:HotRuby está morto.

Infelizmente, apenas duas dessas 11 implementações Ruby são realmente pronto para produção:ressonância magnética e JRuby.

Então, se você quiser threads paralelos verdadeiros, JRuby é atualmente o seu única escolha – não que isso seja ruim:JRuby é realmente mais rápido do que a ressonância magnética, e indiscutivelmente mais estável.

Caso contrário, a solução Ruby "clássica" é usar processos em vez de fios para paralelismo.A biblioteca principal do Ruby contém o Process módulo com o Process.fork método o que torna mais fácil bifurcar outro Ruby processo.Além disso, a Biblioteca Ruby Standard contém oRuby Distribuído (dRuby/dRb) biblioteca, que permite Ruby código a ser distribuído trivialmente em vários processos, não apenas na mesma máquina, mas também em toda a rede.

Outras dicas

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

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

Depende da implementação:

  • A ressonância magnética não tem, o YARV está mais perto.
  • JRuby e MacRuby sim.




Ruby tem fechamentos como Blocks, lambdas e Procs.Para aproveitar ao máximo os encerramentos e múltiplos núcleos no JRuby, Executores de Java seja útil;para MacRuby eu gosto Filas do GCD.

Observe que, sendo capaz de criar threads reais no "nível do sistema operacional" não significa 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 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 de sistema operacional, porém apenas aquele com estado R está correndo.Isto se deve a uma limitação na forma como os threads do Ruby são implementados.



Mesmo programa, agora com JRuby.Você pode ver três threads com estado R, o que significa que eles estão rodando 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.Existem também três threads rodando em paralelo.Isto é porque Threads MacRuby são threads POSIX (threads reais no "nível do sistema operacional") e aqui está sem 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 em Ruby multi-threading, você pode encontrar meu relatório Depurando programas paralelos usando manipuladores de fork interessante.
Para uma visão geral mais geral dos componentes internos do Ruby Rubi sob um microscópio é uma boa leitura.
Também, Ruby Threads e o Global Interpreter Lock em C no Omniref explica no código-fonte porque os threads Ruby não rodam em paralelo.

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 tem um atrito bastante baixo.

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 rodando em uma máquina i7 (4 núcleos hiperthreaded) em ambos os casos...a primeira execução é com:

JRUBY 1.5.6 (Ruby 1.8.7 PatchLevel 249) (2014-02-03 6586) (VM de 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 é maior para threads JRuby, mas o tempo para conclusão é um pouco menor para o Ruby interpretado.É meio difícil dizer pelo gráfico, mas a segunda execução (Ruby interpretada) usa cerca de metade das CPUs (sem hyperthreading?)

enter image description here

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, poderá escrever o código encadeado em C como uma extensão ou usando a gema ruby-inline.

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 são provavelmente uma alternativa melhor.
Mas definitivamente vale a pena tentar primeiro os threads no JRuby.

Além disso, se você estiver interessado no futuro do threading em Ruby, você pode encontrar isto artigo útil.

Aqui estão algumas informações sobre Rinda, que é a implementação Ruby de Linda (processamento paralelo 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 (08/05/2017)

Este artigo é muito antigo, e as informações não são seguidas atuais (2017) pisar, segue alguns suplementos:

  1. Opala é um compilador de fonte a fonte de Ruby para JavaScript.Ele também possui uma implementação do corelib Ruby, possui um desenvolvimento muito ativo e existe uma grande quantidade de frameworks (frontend) trabalhados nele.e produção pronta.Porque é baseado em javascript, não suporta threads paralelos.

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

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top