The Codest
  • Sobre nós
  • Serviços
    • Desenvolvimento de software
      • Desenvolvimento de front-end
      • Desenvolvimento backend
    • Staff Augmentation
      • Programadores Frontend
      • Programadores de back-end
      • Engenheiros de dados
      • Engenheiros de nuvem
      • Engenheiros de GQ
      • Outros
    • Aconselhamento
      • Auditoria e consultoria
  • Indústrias
    • Fintech e Banca
    • E-commerce
    • Adtech
    • Tecnologia da saúde
    • Fabrico
    • Logística
    • Automóvel
    • IOT
  • Valor para
    • CEO
    • CTO
    • Gestor de entregas
  • A nossa equipa
  • Case Studies
  • Saber como
    • Blogue
    • Encontros
    • Webinars
    • Recursos
Carreiras Entrar em contacto
  • Sobre nós
  • Serviços
    • Desenvolvimento de software
      • Desenvolvimento de front-end
      • Desenvolvimento backend
    • Staff Augmentation
      • Programadores Frontend
      • Programadores de back-end
      • Engenheiros de dados
      • Engenheiros de nuvem
      • Engenheiros de GQ
      • Outros
    • Aconselhamento
      • Auditoria e consultoria
  • Valor para
    • CEO
    • CTO
    • Gestor de entregas
  • A nossa equipa
  • Case Studies
  • Saber como
    • Blogue
    • Encontros
    • Webinars
    • Recursos
Carreiras Entrar em contacto
Seta para trás VOLTAR
2016-10-06
Desenvolvimento de software

FORKING E THREADING EM RUBY

Marek Gierlach

Como provavelmente sabe, o Ruby tem algumas implementações, tais como MRI, JRuby, Rubinius, Opal, RubyMotion, etc., e cada uma delas pode usar um padrão diferente de execução de código. Este artigo centrar-se-á nas três primeiras e comparará o MRI

Como provavelmente sabe, Rubi tem algumas implementações, tais como MRI, JRuby, Rubinius, Opal, RubyMotion, etc., e cada uma delas pode utilizar um padrão diferente de código execução. Este artigo centrar-se-á nos três primeiros e comparará o MRI (atualmente a implementação mais popular) com o JRuby e o Rubinius, executando alguns exemplos de scripts que deverão avaliar a adequação da bifurcação e do threading em várias situações, como o processamento de algoritmos com uso intensivo de CPU, a cópia de ficheiros, etc. Antes de começar a "aprender fazendo", é necessário rever alguns termos básicos.

Garfo

  • é um novo processo filho (uma cópia do processo pai)
  • tem um novo identificador de processo (PID)
  • tem memória separada*
  • comunica com outros através de canais de comunicação inter-processos (IPC), como filas de mensagens, ficheiros, tomadas, etc.
  • existe mesmo quando o processo principal termina
  • é uma chamada POSIX - funciona principalmente em plataformas Unix

Linha

  • é "apenas" um contexto de execução, funcionando no âmbito de um processo
  • partilha toda a memória com outros (por defeito, utiliza menos memória do que um fork)
  • comunica com outros através de objectos de memória partilhada
  • morre com um processo
  • introduz problemas típicos de multithreading, como a fome, os bloqueios, etc.

Existem muitas ferramentas que utilizam forks e threads e que são utilizadas diariamente, por exemplo, o Unicorn (forks) e o Puma (threads) ao nível dos servidores de aplicações, o Resque (forks) e o Sidekiq (threads) ao nível dos trabalhos em segundo plano, etc.

A tabela seguinte apresenta o suporte para forking e threading nas principais implementações de Ruby.

Implementação do RubyBifurcaçãoEnfiamento
RMNSimSim (limitado pelo GIL**)
JRuby–Sim
RubiniusSimSim

Mais duas palavras mágicas estão a voltar como um boomerang neste tópico - paralelismo e concorrência - precisamos de as explicar um pouco. Antes de mais, estes termos não podem ser utilizados indistintamente. Em poucas palavras - podemos falar de paralelismo quando duas ou mais tarefas estão a ser processadas exatamente ao mesmo tempo. A concorrência ocorre quando duas ou mais tarefas estão a ser processadas em períodos de tempo sobrepostos (não necessariamente ao mesmo tempo). Sim, é uma explicação abrangente, mas suficientemente boa para o ajudar a perceber a diferença e a compreender o resto deste artigo.

Relatório Fronented para 2020

O quadro seguinte apresenta o suporte para paralelismo e concorrência.

Implementação do RubyParalelismo (via forks)Paralelismo (através de threads)Concorrência
RMNSimNãoSim
JRuby–SimSim
RubiniusSimSim (desde a versão 2.X)Sim

Acabou-se a teoria - vamos ver na prática!

  • Ter memória separada não implica necessariamente consumir a mesma quantidade de memória que o processo principal. Existem algumas técnicas de otimização da memória. Uma delas é a Copy on Write (CoW), que permite ao processo pai partilhar a memória atribuída com o processo filho sem a copiar. Com a CoW, só é necessária memória adicional no caso de modificação da memória partilhada por um processo filho. No contexto do Ruby, nem todas as implementações são favoráveis à CoW, por exemplo, o MRI suporta-a totalmente desde a versão 2.X. Antes desta versão, cada bifurcação consumia tanta memória como um processo pai.
  • Uma das maiores vantagens/desvantagens do MRI (risque a alternativa inadequada) é a utilização do GIL (Global Interpreter Lock). Em poucas palavras, esse mecanismo é responsável por sincronizar a execução das threads, o que significa que apenas uma thread pode ser executada por vez. Mas espere... Isso significa que não faz sentido usar threads no MRI? A resposta vem com o entendimento da parte interna do GIL... ou pelo menos dando uma olhada nos exemplos de código deste artigo.

Caso de teste

Para apresentar como funciona o forking e o threading nas implementações do Ruby, criei uma classe simples chamada Teste e algumas outras que dela herdam. Cada classe tem uma tarefa diferente para processar. Por defeito, cada tarefa é executada quatro vezes num ciclo. Além disso, cada tarefa é executada em três tipos de execução de código: seqüencial, com bifurcações e com threads. Além disso, Referência.bmbm executa o bloco de código duas vezes - a primeira vez para colocar o ambiente de tempo de execução em funcionamento, a segunda vez para medir. Todos os resultados apresentados neste artigo foram obtidos na segunda execução. É claro que, mesmo bmbm não garante um isolamento perfeito, mas as diferenças entre várias execuções de código são insignificantes.

requerer "benchmark"

classe Teste
  MONTANTE = 4

  def run
    Benchmark.bmbm do |b|
      b.report("sequencial") { sequencial }
      b.report("forking") { forking }
      b.report("threading") { threading }
    end
  fim

  privado

  def sequencial
    MONTANTE.vezes { executar }
  end

  def bifurcação
    AMOUNT.times do
      fork do
        executar
      fim
    fim

    Process.waitall
  rescue NotImplementedError => e
    # O método fork não está disponível no JRuby
    puts e
  end

  def threading
    threads = []

    AMOUNT.times do
      threads << Thread.new do
        executar
      fim
    fim

    threads.map(&:join)
  end

  def perform
    raise "não implementado"
  end
fim
Teste de carga

Executa cálculos num ciclo para gerar uma grande carga de CPU.

classe LoadTest < Teste
  def perform
    1000.vezes { 1000.vezes { 2**3**4 } }
  end
fim

Vamos lá a correr...

LoadTest.new.run

...e verificar os resultados

RMNJRubyRubinius
sequencial1.8629282.0890001.918873
bifurcação0.945018–1.178322
enfiamento1.9139821.1070001.213315

Como se pode ver, os resultados das execuções sequenciais são semelhantes. É claro que há uma pequena diferença entre as soluções, mas ela é causada pela implementação subjacente dos métodos escolhidos em vários intérpretes.

A bifurcação, neste exemplo, tem um ganho de desempenho significativo (o código é executado quase duas vezes mais rápido).

A utilização de threads dá resultados semelhantes aos da bifurcação, mas apenas para JRuby e Rubinius. Executar a amostra com threads no MRI consome um pouco mais de tempo do que o método sequencial. Há pelo menos duas razões. Em primeiro lugar, o GIL força a execução de threads sequenciais, portanto, num mundo perfeito, o tempo de execução deveria ser o mesmo da execução sequencial, mas também ocorre uma perda de tempo para as operações do GIL (alternar entre threads, etc.). Em segundo lugar, também é necessário algum tempo de sobrecarga para a criação de threads.

Este exemplo não dá nós uma resposta à pergunta sobre o sentido dos fios de utilização na ressonância magnética. Vamos ver outra.

Teste da soneca

Executa um método sleep.

classe SnoozeTest < Teste
  def executar
    dormir 1
  fim
fim

Eis os resultados

RMNJRubyRubinius
sequencial4.0046204.0060004.003186
bifurcação1.022066–1.028381
enfiamento1.0015481.0040001.003642

Como se pode ver, cada implementação dá resultados semelhantes não só nas execuções sequenciais e forking, mas também nas execuções com threads. Então, porque é que o MRI tem o mesmo ganho de desempenho que o JRuby e o Rubinius? A resposta está na implementação de dormir.

Ressonâncias magnéticas dormir é implementado com o método rb_thread_wait_for C, que utiliza uma outra função chamada nativo_dormir. Vamos dar uma olhadela rápida à sua implementação (o código foi simplificado, a implementação original pode ser encontrada aqui):

static void
native_sleep(rb_thread_t *th, struct timeval *timeout_tv)
{
  ...

  GVL_UNLOCK_BEGIN();
  {
    // faz algumas coisas aqui
  }
  GVL_UNLOCK_END();

  thread_debug("native_sleep donen");
 }

A razão pela qual esta função é importante é que, para além de utilizar o contexto estrito do Ruby, também muda para o contexto do sistema para efetuar algumas operações. Em situações como esta, o processo Ruby não tem nada para fazer... Grande exemplo de perda de tempo? Nem por isso, porque existe um GIL que diz: "Nada para fazer nesta thread? Vamos mudar para outra e voltar aqui depois de um tempo". Isto pode ser feito desbloqueando e bloqueando a GIL com GVL_UNLOCK_BEGIN() e GVL_UNLOCK_END() funções.

A situação torna-se clara, mas dormir raramente é útil. Precisamos de mais exemplos da vida real.

Teste de descarregamento de ficheiros

Executa um processo que descarrega e guarda um ficheiro.

requer "net/http"

classe DownloadFileTest < Teste
  def executar
    Net::HTTP.get("upload.wikimedia.org", "/wikipedia/commons/thumb/7/73/Ruby_logo.svg/2000px-Ruby_logo.svg.png")
  fim
fim

Não é necessário comentar os resultados seguintes. São bastante semelhantes aos do exemplo anterior.

1.003642JRubyRubinius
sequencial0.3279800.3340000.329353
bifurcação0.104766–0.121054
enfiamento0.0857890.0940000.088490

Outro bom exemplo pode ser o processo de cópia de ficheiros ou qualquer outra operação de E/S.

Conclusões

  • Rubinius suporta totalmente tanto a bifurcação como o encadeamento (desde a versão 2.X, quando o GIL foi removido). O seu código pode ser concorrente e executado em paralelo.
  • JRuby faz um bom trabalho com threads, mas não suporta bifurcação de forma alguma. O paralelismo e a concorrência podem ser alcançados com threads.
  • RMN suporta forking, mas o threading é limitado pela presença do GIL. A concorrência pode ser alcançada com threads, mas apenas quando o código em execução sai do contexto do interpretador Ruby (por exemplo, operações de IO, funções do kernel). Não há forma de conseguir paralelismo.

Artigos relacionados

Ilustração de uma aplicação de cuidados de saúde para smartphone com um ícone de coração e um gráfico de saúde em ascensão, com o logótipo The Codest, representando soluções digitais de saúde e HealthTech.
Desenvolvimento de software

Softwares para o setor de saúde: Tipos, casos de uso

As ferramentas em que as organizações de cuidados de saúde confiam atualmente não se assemelham em nada às fichas de papel de há décadas atrás. O software de cuidados de saúde apoia agora os sistemas de saúde, os cuidados aos doentes e a prestação de cuidados de saúde modernos em...

OCODEST
Ilustração abstrata de um gráfico de barras em declínio com uma seta ascendente e uma moeda de ouro que simboliza a eficiência ou a poupança de custos. O logótipo The Codest aparece no canto superior esquerdo com o slogan "In Code We Trust" sobre um fundo cinzento claro
Desenvolvimento de software

Como dimensionar a sua equipa de desenvolvimento sem perder a qualidade do produto

Aumentar a sua equipa de desenvolvimento? Saiba como crescer sem sacrificar a qualidade do produto. Este guia cobre sinais de que é hora de escalar, estrutura da equipe, contratação, liderança e ferramentas - além de como o The Codest pode...

OCODEST
Desenvolvimento de software

Construir aplicações Web preparadas para o futuro: ideias da equipa de especialistas do The Codest

Descubra como o The Codest se destaca na criação de aplicações web escaláveis e interactivas com tecnologias de ponta, proporcionando experiências de utilizador perfeitas em todas as plataformas. Saiba como a nossa experiência impulsiona a transformação digital e o negócio...

OCODEST
Desenvolvimento de software

As 10 principais empresas de desenvolvimento de software sediadas na Letónia

Saiba mais sobre as principais empresas de desenvolvimento de software da Letónia e as suas soluções inovadoras no nosso último artigo. Descubra como estes líderes tecnológicos podem ajudar a elevar o seu negócio.

thecodest
Soluções para empresas e escalas

Fundamentos do desenvolvimento de software Java: Um Guia para Terceirizar com Sucesso

Explore este guia essencial sobre o desenvolvimento de software Java outsourcing com sucesso para aumentar a eficiência, aceder a conhecimentos especializados e impulsionar o sucesso do projeto com The Codest.

thecodest

Subscreva a nossa base de conhecimentos e mantenha-se atualizado sobre os conhecimentos do sector das TI.

    Sobre nós

    The Codest - Empresa internacional de desenvolvimento de software com centros tecnológicos na Polónia.

    Reino Unido - Sede

    • Office 303B, 182-184 High Street North E6 2JA
      Londres, Inglaterra

    Polónia - Pólos tecnológicos locais

    • Parque de escritórios Fabryczna, Aleja
      Pokoju 18, 31-564 Cracóvia
    • Embaixada do Cérebro, Konstruktorska
      11, 02-673 Varsóvia, Polónia

      The Codest

    • Início
    • Sobre nós
    • Serviços
    • Case Studies
    • Saber como
    • Carreiras
    • Dicionário

      Serviços

    • Aconselhamento
    • Desenvolvimento de software
    • Desenvolvimento backend
    • Desenvolvimento de front-end
    • Staff Augmentation
    • Programadores de back-end
    • Engenheiros de nuvem
    • Engenheiros de dados
    • Outros
    • Engenheiros de GQ

      Recursos

    • Factos e mitos sobre a cooperação com um parceiro externo de desenvolvimento de software
    • Dos EUA para a Europa: Porque é que as empresas americanas decidem mudar-se para a Europa?
    • Comparação dos centros de desenvolvimento da Tech Offshore: Tech Offshore Europa (Polónia), ASEAN (Filipinas), Eurásia (Turquia)
    • Quais são os principais desafios dos CTOs e dos CIOs?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Website terms of use

    Direitos de autor © 2026 por The Codest. Todos os direitos reservados.

    pt_PTPortuguese
    en_USEnglish de_DEGerman sv_SESwedish da_DKDanish nb_NONorwegian fiFinnish fr_FRFrench pl_PLPolish arArabic it_ITItalian jaJapanese es_ESSpanish nl_NLDutch etEstonian elGreek cs_CZCzech pt_PTPortuguese