Os processos do Elixir são isolados e não compartilham nada por padrão. Quando um processo filho sem link quebra, o processo pai não é afetado.
Esse comportamento pode ser alterado ao ligar processos uns aos outros. Se dois processos estiverem ligados, uma falha em um deles será propagada para o outro. Os links são bidirecionais.
Processos podem ser criados já ligados ao processo chamador usando spawn_link/1, que é uma operação atômica, ou podem ser ligados depois com Process.link/1.
Ligar processos pode ser útil ao fazer trabalho paralelizado, quando cada pedaço de trabalho não deve continuar caso outro pedaço não consiga terminar.
Os links também podem ser usados para supervisionar processos. Se um processo captura saídas, ele não vai quebrar quando um processo ao qual está ligado quebrar. Em vez disso, ele vai receber uma mensagem sobre a quebra. Isso permite lidar com a quebra de forma tranquila, por exemplo reiniciando o processo que quebrou.
Um processo pode ser configurado para capturar saídas chamando Process.flag(:trap_exit, true). Repare que Process.flag/2 retorna o valor antigo da flag, não o novo.
A mensagem que será enviada ao processo caso um processo ligado quebre vai corresponder ao padrão {:EXIT, from, reason}, em que from é um PID. Se reason for qualquer coisa diferente do átomo :normal, isso significa que o processo quebrou ou foi morto à força.
Tarefas são processos destinados a executar uma operação específica. Normalmente elas não se comunicam com outros processos, mas podem retornar um resultado para o processo que iniciou a tarefa.
As tarefas são comumente usadas para paralelizar trabalho.
async/await
Para iniciar uma tarefa, use Task.async/1. Ela recebe uma função anônima como argumento e a executa em um novo processo que está ligado ao processo chamador. Ela retorna uma struct %Task{}.
Para obter o resultado da execução, passe a struct %Task{} para Task.await/2. Ela vai esperar a tarefa terminar e retornar seu resultado. O segundo argumento é um timeout em milissegundos, com valor padrão de 5000.
Repare que, entre iniciar a tarefa e aguardá-la, o processo que iniciou a tarefa não fica bloqueado e pode fazer outras operações.
Qualquer tarefa iniciada com Task.async/1 deve ser aguardada, porque ela vai enviar uma mensagem ao processo chamador. Só é possível chamar Task.await/2 uma vez para cada tarefa.
start/start_link
Se você quiser iniciar uma tarefa apenas para efeitos colaterais, use Task.start/1 ou Task.start_link/1. Task.start/1 vai iniciar uma tarefa que não está ligada ao processo chamador, e Task.start_link/1 vai iniciar uma tarefa que está ligada ao processo chamador. As duas funções retornam uma tupla {:ok, pid}.
Seu trabalho na Instruments of Texas com uma calculadora RPN experimental continua. Sua equipe construiu alguns protótipos que precisam passar por uma inspeção minuciosa, para escolher o melhor que possa ser produzido em massa.
Você quer realizar dois tipos de verificação.
Primeiro, uma verificação de confiabilidade que vai detectar entradas para as quais a calculadora sob inspeção trava ou não responde rápido o suficiente. Para isolar falhas, os cálculos de cada entrada precisam rodar em um processo separado. Vincular processos e capturar saídas no processo que faz a chamada podem ser usados para detectar se o cálculo terminou ou travou.
Segundo, uma verificação de correção que vai checar se, para uma dada entrada, o resultado retornado pela calculadora é o esperado. Só as calculadoras que já passaram na verificação de confiabilidade vão passar por uma verificação de correção, então travamentos não são uma preocupação. No entanto, as operações devem rodar de forma concorrente para acelerar o processo, o que faz disso o caso de uso perfeito para tarefas assíncronas.
Implemente a função RPNCalculatorInspection.start_reliability_check/2. Ela deve receber 2 argumentos, uma função (a calculadora) e uma entrada para a calculadora. Ela deve retornar um mapa que contém a entrada e o PID do processo criado.
O processo criado deve chamar a função calculadora dada com a entrada dada. O processo deve estar vinculado ao processo que fez a chamada.
RPNCalculatorInspection.start_reliability_check(fn _ -> 0 end, "2 3 +")
# => %{input: "2 3 +", pid: #PID<0.169.0>}
Implemente a função RPNCalculatorInspection.await_reliability_check_result/2. Ela deve receber dois argumentos. O primeiro argumento é um mapa com a entrada da verificação de confiabilidade e o PID do processo que roda a verificação de confiabilidade para essa entrada, conforme retornado por RPNCalculatorInspection.start_reliability_check/2. O segundo argumento é um mapa que serve como acumulador dos resultados de verificações de confiabilidade com entradas diferentes.
A função deve esperar por uma mensagem de saída.
Se ela receber uma mensagem de saída ({:EXIT, from, reason}) com o motivo :normal do mesmo processo que roda a verificação de confiabilidade, ela deve retornar o mapa de resultados com o valor :ok adicionado sob a chave input.
Se ela receber uma mensagem de saída com um motivo diferente do mesmo processo que roda a verificação de confiabilidade, ela deve retornar o mapa de resultados com o valor :error adicionado sob a chave input.
Se ela não receber nenhuma mensagem que atenda a esses critérios em 100ms, ela deve retornar o mapa de resultados com o valor :timeout adicionado sob a chave input.
# when an exit message is waiting for the process in its inbox
send(self(), {:EXIT, pid, :normal})
RPNCalculatorInspection.await_reliability_check_result(
%{input: "5 7 -", pid: pid},
%{}
)
# => %{"5 7 -" => :ok}
# when there are no messages in the process inbox
RPNCalculatorInspection.await_reliability_check_result(
%{input: "3 2 *", pid: pid},
%{"5 7 -" => :ok}
)
# => %{"5 7 -" => :ok, "3 2 *" => :timeout}
Implemente a função RPNCalculatorInspection.reliability_check/2. Ela deve receber 2 argumentos, uma função (a calculadora) e uma lista de entradas para a calculadora.
Para cada entrada da lista, ela deve iniciar a verificação de confiabilidade em um novo processo vinculado usando start_reliability_check/2. Depois, para cada processo iniciado dessa forma, ela deve aguardar seus resultados usando await_reliability_check_result/2.
Antes de iniciar qualquer processo, a função precisa marcar o processo atual para capturar saídas, para poder receber mensagens de saída. Depois, ela deve redefinir essa marcação para o valor original.
A função deve retornar um mapa com os resultados das verificações de confiabilidade de todas as entradas.
fake_broken_calculator = fn input ->
if String.ends_with?(input, "*"), do: raise("oops")
end
inputs = ["2 3 +", "10 3 *", "20 2 /"]
RPNCalculatorInspection.reliability_check(fake_broken_calculator, inputs)
# => %{
# "2 3 +" => :ok,
# "10 3 *" => :error,
# "20 2 /" => :ok
# }
Implemente a função RPNCalculatorInspection.correctness_check/2. Ela deve receber 2 argumentos, uma função (a calculadora) e uma lista de entradas para a calculadora.
Para cada entrada da lista, ela deve iniciar uma tarefa assíncrona que vai chamar a calculadora com a entrada dada. Depois, para cada tarefa iniciada dessa forma, ela deve aguardar seus resultados por 100ms.
fast_cheating_calculator = fn input -> 14 end
inputs = ["13 1 +", "50 2 *", "1000 2 /"]
RPNCalculatorInspection.correctness_check(fast_cheating_calculator, inputs)
# => [14, 14, 14]
Crie sua conta no Exercism para aprender e dominar Elixir com 58 conceitos168 exercícios e mentoria humana de verdade, tudo de graça.