Pr

Processos em Elixir

5 exercícios

Sobre Processos

Em Elixir, todo código roda dentro de processos. Processos Elixir:

  • Não devem ser confundidos com processos do sistema.
  • São leves.
  • Têm casos de uso específicos. Eles podem:
  • É normal ter um app Elixir que roda centenas de processos, mas também um que não cria novos processos explicitamente, especialmente se for uma biblioteca.

Criando processos

Por padrão, uma função é executada no mesmo processo de onde foi chamada. Quando você precisa executar explicitamente uma função específica em um novo processo, use spawn:

  • spawn/1 aceita uma função e a executa diretamente.

    spawn(fn -> 2 + 2 end)
    # => #PID<0.125.0>
    
  • spawn/3 aceita o nome de um módulo, o nome de uma função (como átomo) e uma lista de argumentos, e executa essa função.

    spawn(String, :split, ["hello there", " "])
    # => #PID<0.113.0>
    
    • Esse trio de dados costuma ser chamado de MFA, que significa Module, Function, Arguments.
  • Um processo termina assim que sua função termina de executar.

  • Você pode verificar se um processo ainda está vivo (executando) com Process.alive?/1:

    pid = spawn(fn -> 2 + 2 end)
    Process.alive?(pid)
    # => false
    

Mensagens

Processos não compartilham informações diretamente uns com os outros. Processos enviam mensagens para compartilhar dados. Esse padrão de concorrência é chamado de modelo de ator.

  • Envie mensagens para um processo usando send/2.

    send(pid, :hello)
    
    • A mensagem acaba na caixa de mensagens do destinatário na ordem em que foram enviadas.
    • send não verifica se a mensagem foi recebida nem se o destinatário ainda está vivo.
  • Uma mensagem pode ser de qualquer tipo.

  • Você pode receber uma mensagem enviada ao processo atual usando receive/1.

    • Você precisa fazer pattern matching nas mensagens.
    • receive espera até que uma mensagem que corresponda a algum dos padrões esteja na caixa de mensagens do processo.
      • Por padrão, espera indefinidamente, mas pode receber um timeout usando um bloco after.
    • Mensagens lidas são removidas da caixa de mensagens do processo. Mensagens não lidas permanecerão lá indefinidamente.
      • Sempre escreva uma cláusula _ que captura tudo em receive/1 para evitar ficar sem memória devido a mensagens não lidas acumuladas.
    receive do
      {:ping, sender_pid} -> send(sender_pid, :pong)
      _ -> nil
    after
      5000 ->
        {:error, "No message in 5 seconds"}
    end
    

Laço de recebimento

Se você quer receber mais de uma mensagem, precisa chamar receive/1 recursivamente. É um padrão comum implementar uma função recursiva, por exemplo chamada loop, que chama receive/1, faz algo com a mensagem e depois se chama para esperar mais mensagens. Se você precisa carregar algum estado de uma chamada de receive/1 para outra, pode fazer isso passando um argumento para essa função loop.

def loop(state) do
  receive do
    :increment_by_one ->
      loop(state + 1)

    {:report_state, sender_pid} ->
      send(sender_pid, state)
      loop(state)

    :stop ->
      nil

    _ ->
      loop(state)
  end
end

Na prática, essa abordagem raramente é usada diretamente. Elixir oferece abstrações de concorrência, como o módulo Agent ou o comportamento GenServer, que ambos são construídos sobre o laço de recebimento. No entanto, é crucial entender esses fundamentos para poder usar as abstrações de forma eficiente.

Editar via GitHub O link abre em uma nova janela ou aba

Aprenda Processos

A prática está bloqueada

Desbloqueie mais 4 exercícios para praticar Processos