Pr

Processos em Elixir

5 exercícios

Sobre Processos

Em Elixir, todo o código corre dentro de processos. Os processos em Elixir:

  • Não devem ser confundidos com processos do sistema.
  • São leves.
  • Têm casos de uso específicos. Podem:
  • É normal ter uma aplicação Elixir que corre centenas de processos, mas também uma que não cria novos processos de forma explícita, sobretudo se for uma biblioteca.

Criar processos

Por predefinição, uma função é executada no mesmo processo a partir do qual foi chamada. Quando precisas de executar uma determinada função num novo processo, usa spawn:

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

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

    spawn(String, :split, ["hello there", " "])
    # => #PID<0.113.0>
    
    • A este triplo de dados chama-se frequentemente MFA (Módulo, Função, Argumentos).
  • Um processo termina assim que a sua função acaba de ser executada.

  • Podes verificar se um processo ainda está vivo (a executar) com Process.alive?/1:

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

Mensagens

Os processos não partilham informação diretamente entre si. Os processos enviam mensagens para partilhar dados. Este padrão de concorrência chama-se modelo de atores.

  • Envia mensagens a um processo com send/2.

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

  • Podes receber uma mensagem enviada ao processo atual com receive/1.

    • Tens de fazer correspondência de padrões nas mensagens.
    • O receive espera até haver na caixa de correio do processo uma mensagem que corresponda a algum dos padrões indicados.
      • Por predefinição, espera indefinidamente, mas pode receber um tempo limite através de um bloco after.
    • As mensagens lidas são removidas da caixa de correio do processo. As mensagens não lidas ficam lá indefinidamente.
      • Escreve sempre uma cláusula _ que apanhe tudo no receive/1 para evitares ficar sem memória devido ao acumular de mensagens não lidas.
    receive do
      {:ping, sender_pid} -> send(sender_pid, :pong)
      _ -> nil
    after
      5000 ->
        {:error, "No message in 5 seconds"}
    end
    

Ciclo de receção

Se quiseres receber mais do que uma mensagem, tens de chamar o receive/1 de forma recursiva. É um padrão comum implementar uma função recursiva, por exemplo chamada loop, que chama o receive/1, faz algo com a mensagem e depois se chama a si própria para esperar por mais mensagens. Se precisares de transportar algum estado de uma chamada ao receive/1 para outra, podes fazê-lo passando um argumento a 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, esta abordagem raramente é usada diretamente. O Elixir oferece abstrações de concorrência, como o módulo Agent ou o comportamento GenServer, que assentam ambos no ciclo de receção. No entanto, é crucial compreender estes conceitos básicos para conseguires usar as abstrações de forma eficiente.

Editar via GitHub A ligação abre numa nova janela ou separador

Aprende Processos

A prática está bloqueada

Desbloqueia mais 4 exercícios para praticares Processos