concorrencia101
  • Introdução
  • First things first
  • Agradecimentos
  • Parte I - Concorrência no sistema operacional
    • O que é o programa no sistema operacional
    • Escalonador preemptivo de tarefas
    • Uma nota sobre escalonamento cooperativo
    • Propriedades de um processo
    • Clone de processo (forking)
    • Clone leve de processo (thread)
    • Todo processo tem uma thread principal
    • Uma nota sobre paralelismo
    • Principais desafios em cenário de concorrência
      • Race condition
      • Sincronização com locks
      • Modelo de atores
    • E o I/O?
      • Latência de CPU vs Latência de I/O
      • Chamadas bloqueantes
      • Chamadas não-bloqueantes
      • Assincronismo e escalonamento cooperativo
    • Vamos colocar em prática...
  • PARTE II - Concorrência em diferentes linguagens
    • Definindo ambientes de execução
    • Concorrência em C
      • Forking de processos
      • Threads
      • Race condition e sincronização de threads com mutex
      • Desafios com o uso de threads
      • Thread Pool em C
      • Green threads
      • Modelo de Atores
      • Trabalhando com I/O
    • Concorrência em Ruby
      • Forking de processos
      • Threads
      • Race condition, YARV, GVL e paralelismo em Ruby
      • Modelo de Atores
      • Trabalhando com I/O
Powered by GitBook
On this page
  • Um exemplo mais robusto
  • Comunicação entre processos (IPC)
  1. PARTE II - Concorrência em diferentes linguagens
  2. Concorrência em Ruby

Forking de processos

PreviousConcorrência em RubyNextThreads

Last updated 4 months ago

Para aqueles que já leram a , não terão qualquer dificuldade em entender os conceitos aqui abordados. E caso também você tenha real interesse em aprender sobre concorrência, certamente já leu como funciona .

Em Ruby, existe um método especial chamado fork que, na hora de ser executado, faz a chamada de sistema fork.

Vamos ver o primeiro exemplo similar ao que vimos inicialmente no módulo de C:

forking.rb
fork
puts "Hello"

Agora vamos executar o programa utilizando o interpretador CRuby chamando ruby forking.rb , que imprime:

Hello
Hello

So far, so good. Já vimos anteriormente o motivo de aparecer Hello 2 vezes. Em um outro exemplo, podemos ver que o pid é diferente dependendo se estamos no parent ou no child:

pid = fork

if pid
  puts "In parent, pid is #{Process.pid}. Child is #{pid}"
else
  puts "In child, pid is #{Process.pid}"
end
In parent, pid is 77278. Child is 77430
In child, pid is 77430

Leiam a , sempre

Um exemplo mais robusto

Agora, vamos a um exemplo um pouco mais robusto com o uso de fork de processos, onde um processo pai dispara 3 processos filhos que irão executar uma tarefa que demora 2 segundos cada:

forking.rb
def perform
  pid = Process.pid

  puts "Processo filho (PID: #{pid}) executando tarefa..."
  sleep(2) # Simula uma tarefa que leva 2 segundos
  puts "Processo filho (PID: #{pid}) completou a tarefa!"
end

wait_pids = []

# Criação dos processos filhos
3.times do
  pid = fork do
    perform # Código do processo filho
  end

  wait_pids << pid # Armazena o PID do filho para controle
end

# O processo pai aguarda cada filho terminar
wait_pids.each do |child_pid|
  Process.wait(child_pid)

  puts "Pai: Processo filho com PID #{child_pid} terminou."
end

puts "Pai: Todos os filhos terminaram. Finalizando."
Processo filho (PID: 78317) executando tarefa...
Processo filho (PID: 78319) executando tarefa...
Processo filho (PID: 78318) executando tarefa...
Processo filho (PID: 78318) completou a tarefa!
Processo filho (PID: 78317) completou a tarefa!
Processo filho (PID: 78319) completou a tarefa!
Pai: Processo filho com PID 78317 terminou.
Pai: Processo filho com PID 78318 terminou.
Pai: Processo filho com PID 78319 terminou.
Pai: Todos os filhos terminaram. Finalizando.

Interessante notar aqui:

  • os 3 processos filhos foram criados e iniciaram sua execução nesta ordem (78317, 78318, 78319)

  • entretanto, de acordo com as mensagens, o processo 78318 terminou antes dos outros 2

  • mas para o processo pai, o filho que terminou primeiro foi o 78317

Isto, senhoras e senhores, é a maravilha da concorrência. Não temos controle algum sobre a ordem e execução das tarefas!

Sim, vou repetir isso inúmeras vezes neste guia kk

Comunicação entre processos (IPC)

# Cria um pipe com dois descritores (leitura e escrita)
read_fd, write_fd = IO.pipe

pid = fork do
  # Processo filho
  read_fd.close # Fecha a extremidade de leitura no filho
  mensagem = "Message from child!"
  write_fd.puts(mensagem) # Escreve a mensagem no pipe
  write_fd.close # Fecha a extremidade de escrita no filho
end

# Processo pai
write_fd.close # Fecha a extremidade de escrita no pai
mensagem_recebida = read_fd.gets.chomp # Lê a mensagem do pipe
puts "Parent received message: #{mensagem_recebida}"
read_fd.close # Fecha a extremidade de leitura no pai

# Aguarda o processo filho terminar
Process.wait(pid)

Este é um exemplo bastante simples de como 2 processos distintos podem conversar entre si, através da utilização de UNIX pipes.

Repare como que até o momento, conseguimos fazer em Ruby tudo o que foi possível fazer em C com relação a forking de processos, e isto se deve ao fato de que o interpretador CRuby é escrito em C, o que torna simples a interface para as syscalls envolvidas no sistema operacional.

Agora, chegou o momento de vermos as Threads em Ruby.

Assim como em C, podemos fazer IPC em Ruby utilizando :

parte I do guia
forking de processos em C
documentação
pipes