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 AND THREADING IN RUBY

Marek Gierlach

As you probably know, Ruby has a few implementations, such as MRI, JRuby, Rubinius, Opal, RubyMotion etc., and each of them may use a different pattern of code execution. This article will focus on the first three of them and compare MRI

As you probably know, Rubi has a few implementations, such as MRI, JRuby, Rubinius, Opal, RubyMotion etc., and each of them may use a different pattern of código execution. This article will focus on the first three of them and compare MRI (currently the most popular implementation) with JRuby and Rubinius by running a few sample scripts which are supposed to assess suitability of forking and threading in various situations, such as processing CPU-intensive algorithms, copying files etc.Before you start “learning by doing”, you need to revise a few basic terms.

Fork

  • is a new child process (a copy of the parent one)
  • has a new process identifier (PID)
  • has separate memory*
  • communicates with others via inter-process communication (IPC) channels like message queues, files, sockets etc.
  • exists even when parent process ends
  • is a POSIX call – works mainly on Unix platforms

Thread

  • is “only” an execution context, working within a process
  • shares all of the memory with others (by default it uses less memory than a fork)
  • communicates with others by shared memory objects
  • dies with a process
  • introduces typical multi-threading problems such as starvation, deadlocks etc.

There are plenty of tools using forks and threads, which are being used on a daily basis, e.g. Unicorn (forks) and Puma (threads) on application servers level, Resque (forks) and Sidekiq (threads) on the background jobs level, etc.

The following table presents the support for forking and threading in the major Ruby implementations.

Ruby ImplementationForkingThreading
MRIYesYes (limited by GIL**)
JRuby–Yes
RubiniusYesYes

Two more magic words are coming back like a boomerang in this topic – parallelism and concurrency – we need to explain them a bit. First of all, these terms cannot be used interchangeably. In a nutshell – we can talk about the parallelism when two or more tasks are being processed at exactly the same time. The concurrency takes place when two or more tasks are being processed in overlapping time periods (not necessarily at the same time). Yes, it’s a broad explanation, but good enough to help you notice the difference and understand the rest of this article.

Relatório Fronented para 2020

The following table presents the support for parallelism and concurrency.

Ruby ImplementationParallelism (via forks)Parallelism (via threads)Concurrency
MRIYesNoYes
JRuby–YesYes
RubiniusYesYes (since version 2.X)Yes

That’s the end of the theory – let’s see it in practice!

  • Having separate memory doesn’t necessary cause consuming the same amount of it as the parent process. There are some memory optimization techniques. One of them is Copy on Write (CoW), which allows parent process to share allocated memory with child one without copying it. With CoW additional memory is needed only in the case of shared memory modification by a child process. In the Ruby context, not every implementation is CoW friendly, e.g. MRI supports it fully since the version 2.X. Before this version each fork consumed as much memory as a parent process.
  • One of the biggest advantages/disadvantages of MRI (strike out the inappropriate alternative) is the usage of GIL (Global Interpreter Lock). In a nutshell, this mechanism is responsible for synchronizing execution of threads, which means that only one thread can be executed at a time. But wait… Does it mean there is no point in using threads in MRI at all? The answer comes with the understanding of GIL internals… or at least taking a look at the code samples in this article.

Test Case

In order to present how forking and threading works in Ruby’s implementations, I created a simple class called Teste and a few others inheriting from it. Each class has a different task to process. By default, every task runs four times in a loop. Also, every task runs against three types of code execution: sequential, with forks, and with threads. In addition, Benchmark.bmbm runs the block of code twice – first time in order to get the runtime environment up & running, the second time in order to measure. All of the results presented in this article were obtained in the second run. Of course, even bmbm method does not guarantee perfect isolation, but the differences between multiple code runs are insignificant.

require "benchmark"

class Test
  AMOUNT = 4

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

  private

  def sequential
    AMOUNT.times { perform }
  end

  def forking
    AMOUNT.times do
      fork do
        perform
      end
    end

    Process.waitall
  rescue NotImplementedError => e
    # fork method is not available in JRuby
    puts e
  end

  def threading
    threads = []

    AMOUNT.times do
      threads << Thread.new do
        perform
      end
    end

    threads.map(&:join)
  end

  def perform
    raise "not implemented"
  end
end
Load Test

Runs calculations in a loop to generate big CPU load.

class LoadTest < Test
  def perform
    1000.times { 1000.times { 2**3**4 } }
  end
end

Let’s run it…

LoadTest.new.run

…and check the results

MRIJRubyRubinius
sequential1.8629282.0890001.918873
forking0.945018–1.178322
threading1.9139821.1070001.213315

As you can see, the results from sequential runs are similar. Of course there is a small difference between the solutions, but it’s caused by the underlying implementation of chosen methods in various interpreters.

Forking, in this example, has a significant performance gain (code runs almost two times faster).

Threading gives the similar results as forking, but only for JRuby and Rubinius. Running the sample with threads on MRI consumes a bit more time than the sequential method. There are at least two reasons. Firstly, GIL forces sequential threads execution, therefore in a perfect world the execution time should be the same as for the sequential run, but there also occurs a loss of time for GIL operations (switching between threads etc.). Secondly, there is also needed some overhead time for creating threads.

This example doesn’t give nós an answer to the question about the sense of usage threads in MRI. Let’s see another one.

Snooze Test

Runs a sleep method.

class SnoozeTest < Test
  def perform
    sleep 1
  end
end

Here are the results

MRIJRubyRubinius
sequential4.0046204.0060004.003186
forking1.022066–1.028381
threading1.0015481.0040001.003642

As you can see, each implementation gives similar results not only in the sequential and forking runs, but also in the threading ones. So, why MRI has the same performance gain as JRuby and Rubinius? The answer is in the implementation of sleep.

MRI’s sleep method is implemented with rb_thread_wait_for C function, which uses another one called native_sleep. Let’s have a quick look at it’s implementation (the code was simplified, the original implementation could be found aqui):

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

  GVL_UNLOCK_BEGIN();
  {
    // do some stuff here
  }
  GVL_UNLOCK_END();

  thread_debug("native_sleep donen");
 }

The reason why this function is important is that apart from using strict Ruby context, it also switches to the system one in order to perform some operations there. In situations like this, Ruby process has nothing to do… Great example of time wasting? Not really, because there is a GIL saying: “Nothing to do in this thread? Let’s switch to another one and come back here after a while”. This could be done by unlocking and locking GIL with GVL_UNLOCK_BEGIN() e GVL_UNLOCK_END() funções.

The situation becomes clear, but sleep method is rarely useful. We need more real-life example.

File Downloading Test

Runs a process which downloads and saves a file.

require "net/http"

class DownloadFileTest < Test
  def perform
    Net::HTTP.get("upload.wikimedia.org", "/wikipedia/commons/thumb/7/73/Ruby_logo.svg/2000px-Ruby_logo.svg.png")
  end
end

There is no need to comment the following results. They are pretty similar to those from the example above.

1.003642JRubyRubinius
sequential0.3279800.3340000.329353
forking0.104766–0.121054
threading0.0857890.0940000.088490

Another good example could be the file copying process or any other I/O operation.

Conclusões

  • Rubinius fully supports both forking and threading (since version 2.X, when GIL was removed). Your code could be concurrent and run in parallel.
  • JRuby does a good job with threads, but doesn’t support forking at all. Parallelism and concurrency could be achieved with threads.
  • MRI supports forking, but threading is limited by the presence of GIL. Concurrency could be achieved with threads, but only when running code goes outside of the Ruby interpreter context (e.g IO operations, kernel functions). There is no way to achieve parallelism.

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