Comunicação entre procesos em Elixir

thiagoslima

ThiagosLima

Posted on October 7, 2021

Comunicação entre procesos em Elixir

O foco dessa postagem é explicar a comunicação entre processos em elixir de forma simples em português. Então toda a postagem e os exemplos de código estão nessa língua. Você pode ler a versão em inglês aqui. Quando terminar de ler você terá um entendimento básico de como criar processos e como eles enviam e recebem mensagens.

Como criar processos

Um processo é algo que pode fazer coisas de forma isolada e concorrente. Bem... isso ficou muito vago. Vamos tentar um exemplo mais concreto. Digamos que você seja um rato. Você pode fazer várias coisas que os ratos fazem, como se esconder ou comer queijo por exemplo. Você é independente, não precisa de outras pessoas para fazer o que quer e eles não interferem nas suas atividades. Ou seja, você faz isso de forma isolada. Em um concurso de ratos para ver qual come mais queijo, todos poderiam fazer isso de forma concorrente e paralela. Todos comem ao mesmo tempo e não um de cada vez.

mouse

Agora que temos uma noção do que é um processo, vamos imaginar que você quer fazer um novo amigo, ou seja, outro processo. Então vamos criar o processo gato. É possível fazer isso com a função spawn.

spawn a process

A função spawn cria um novo processo e executa uma função nele. Ela tem duas aridades: spawn/1 recebe uma função e spawn/3 recebe um módulo, uma função como um átomo e uma lista de parâmetros. Aqui vamos utilizar a segunda forma porque vamos definir o Gato em um módulo. A função criar apenas imprime uma mensagem. Você pode colocar o código abaixo em um arquivo chamado gato.exs e executar com elixir gato.exs. Se fizer isso vai ver o resultado comentado.

# gato.exs
defmodule Gato do
  def criar do
    IO.puts("Você criou um gato")
  end
end

# Cria um gato
spawn(Gato, :criar, [])

# Resultado:
# "Você criou um gato"
Enter fullscreen mode Exit fullscreen mode

Comunicação entre processos

Depois de criar um novo processo, seu amigo o gato, você quer falar com ele. Mas lembre-se que os processos são isolados. Imagine que cada um vive em sua própria casa com distanciamento social. Então como se comunicar com ele? Há milhões de anos atrás as pessoas utilizavam um forma de comunicação chamada correio. Os processos se comunicam de forma parecida. Então para falar com seu amigo você vai enviar uma mensagem desse jeito.

send receive messages

Enviando e recebendo mensagens

Enviamos uma mensagem com a função send/2. Passamos para essa função duas coisas: o destinatário da mensagem e qual é a mensagem. Mas como nosso código vai saber enviar a mensagem para o processo gato? Ainda bem que a função spawn retorna pra gente um identificador do processo criado, como se fosse seu endereço. Então vamos salvar o retorno de spawn na variável gato. Agora é só enviar a mensagem.

O gato precisa saber como receber essa mensagem. Para isso a gente definiu o bloco receive. Funciona assim: o gato vai olhar dentro da caixa de correio e decidir o que é importante. Ou seja, para quais mensagens ele quer fazer alguma coisa.

# gato.exs
defmodule Gato do
  def criar do
    receive do
      :e_ai_beleza -> IO.puts("De boas")
    end
  end
end

# Cria um gato
gato = spawn(Gato, :criar, [])

# Manda mensagem
send(gato, :e_ai_beleza)

# Resultado:
# "De boas"
Enter fullscreen mode Exit fullscreen mode

Respondendo de volta

Nosso código tem um problema. Imagine o IO.puts("De boas") como se o gato tivesse falado isso. Mas vocês estão em suas próprias casas, lembra? Então você não ouviu a resposta dele. Como os processos se comunicam com mensagens, ele teria que te mandar uma mensagem de volta.

answer

Ainda bem que você já sabe como enviar uma mensagem. É só fazer o gato responder com send(rato, :de_boas) e está tudo certo! Entretanto como o gato vai saber que a mensagem veio do rato? Para fazer isso, vamos falar na mensagem quem foi o remetente. Temos que mudar o receive do gato para se importar com mensagens que seguem o padrão {:e_ai_beleza, remetente}. Assim ele pode responder para a pessoa certa com send(remetente, :de_boas). Fica assim:

receive do
  {:e_ai_beleza, remetente} -> send(remetente, :de_boas)
end
Enter fullscreen mode Exit fullscreen mode

Agora o rato precisa se identificar para mandar a mensagem send(gato, {:e_ai_beleza, rato}). Lembre-se que o rato é o processo atual. Para identificar o processo atual no código usamos a função self/0. É como se estivéssemos pegando nosso próprio endereço. Então salvamos seu retorno em uma variável rato = self().

# Esse é você
rato = self()

# Manda mensagem
send(gato, {:e_ai_beleza, rato})
Enter fullscreen mode Exit fullscreen mode

Está quase tudo pronto. Só precisamos ver na caixa de correio do rato a mensagem que o gato enviou e decidir o que fazer. O código todo ficaria assim:

# gato.exs
defmodule Gato do
  def criar do
    receive do
      {:e_ai_beleza, remetente} -> send(remetente, :de_boas)
    end
  end
end

# Esse é você
rato = self()

# Cria um gato
gato = spawn(Gato, :criar, [])

# Manda mensagem
send(gato, {:e_ai_beleza, rato})

receive do
  :de_boas -> IO.puts("Massa!")
end

# Resultado
# Massa!
Enter fullscreen mode Exit fullscreen mode

Processo Temporário

É possível verificar se um processo está vivo com a função Process.alive?/1. Vamos executar o mesmo código acima mas ver quando o processo gato está vivo adicionando IO.puts("Gato está vivo? #{Process.alive?(gato)}").

defmodule Gato do
  def criar do
    receive do
      {:e_ai_beleza, remetente} -> send(remetente, :de_boas)
    end
  end
end

# Esse é você
rato = self()

# Cria um gato
gato = spawn(Gato, :criar, [])

IO.puts("Gato está vivo? #{Process.alive?(gato)}")

# Manda mensagem
send(gato, {:e_ai_beleza, rato})

receive do
  :de_boas -> IO.puts("Massa!")
end

IO.puts("Gato está vivo? #{Process.alive?(gato)}")

# Resultado
# Gato está vivo? true
# Massa!
# Gato está vivo? false
Enter fullscreen mode Exit fullscreen mode

Podemos ver que o processo estava vivo, mas depois de responder ele morreu. Por que isso aconteceu?

dead process

Para entender o que aconteceu vamos olhar a função criar. Aqui a gente definiu um bloco receive. Como o rato recebeu a mensagem de volta, a comunicação está correta. Mas o que acontece depois que o gato responde? Simplesmente não acontece mais nada no processo gato. Sendo assim, ele não precisa mais existir e o processo morre!

def criar do
  receive do
    {:e_ai_beleza, remetente} -> send(remetente, :de_boas)
  end
end
Enter fullscreen mode Exit fullscreen mode

Processo Permanente

Para manter o processo vivo vamos dizer que ele não responde apenas uma mensagem. Ele vai ficar checando das mensagens e respondendo. Podemos fazer isso definindo a função checar_mensagens/0 e passar o bloco receive para ela.

def criar do
  checar_mensagens()
end

def checar_mensagens do
  receive do
    {:e_ai_beleza, remetente} -> send(remetente, :de_boas)
  end

  checar_mensagens()
end
Enter fullscreen mode Exit fullscreen mode

Dessa forma a única coisa que a função criar faz é dizer para o gato checar as mensagens. Agora olhe a última linha de checar_mensagens/0. Ela chama a si mesma! Ou seja sempre que acabar ela volta para o começo. É uma função recursiva. Dessa forma o gato checa as mensagens para sempre e não morre. O código todo fica assim:

defmodule Gato do
  def criar do
    checar_mensagens()
  end

  def checar_mensagens do
    receive do
      {:e_ai_beleza, remetente} -> send(remetente, :de_boas)
    end

    checar_mensagens()
  end
end

# Esse é você
rato = self()

# Cria um gato
gato = spawn(Gato, :criar, [])

IO.puts("Gato está vivo? #{Process.alive?(gato)}")

# Manda mensagem
send(gato, {:e_ai_beleza, rato})

receive do
  :de_boas -> IO.puts("Massa!")
end

IO.puts("Gato está vivo? #{Process.alive?(gato)}")

# Resultado
# Gato está vivo? true
# Massa!
# Gato está vivo? true
Enter fullscreen mode Exit fullscreen mode

Conclusão

Aqui você aprendeu o básico de como criar processos com spawn. Como eles enviam mensagens com send/2 e recebem mensagens com receive. Você viu um pouco sobre como um processo pode morrer e como mantê-lo vivo. E o melhor de tudo, que gatos e ratos podem ser amigos no fim das contas!

💖 💪 🙅 🚩
thiagoslima
ThiagosLima

Posted on October 7, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related