Los procesos de Elixir están aislados y no comparten nada por defecto. Cuando un proceso hijo no enlazado falla, su proceso padre no se ve afectado.
Este comportamiento se puede cambiar enlazando procesos entre sí. Si dos procesos están enlazados, una falla en uno de ellos se propagará al otro. Los enlaces son bidireccionales.
Los procesos se pueden crear ya enlazados al proceso que hace la llamada usando spawn_link/1, que es una operación atómica, o se pueden enlazar después con Process.link/1.
Enlazar procesos puede ser útil al hacer trabajo paralelizado, cuando cada parte del trabajo no debería continuar si otra parte no logra completarse.
El enlace también se puede usar para supervisar procesos. Si un proceso captura salidas, no fallará cuando falle un proceso al que está enlazado. En su lugar, recibirá un mensaje sobre la falla. Esto le permite manejar la falla de forma controlada, por ejemplo reiniciando el proceso que falló.
Un proceso se puede configurar para capturar salidas llamando a Process.flag(:trap_exit, true). Ten en cuenta que Process.flag/2 devuelve el valor anterior de la bandera, no el nuevo.
El mensaje que se enviará al proceso en caso de que falle un proceso enlazado coincidirá con el patrón {:EXIT, from, reason}, donde from es un PID. Si reason es algo distinto del átomo :normal, significa que el proceso falló o fue eliminado por la fuerza.
Las tareas son procesos pensados para ejecutar una operación específica. Por lo general, no se comunican con otros procesos, pero pueden devolver un resultado al proceso que inició la tarea.
Las tareas se usan comúnmente para paralelizar el trabajo.
async/await
Para iniciar una tarea, usa Task.async/1. Toma una función anónima como argumento y la ejecuta en un proceso nuevo que está enlazado al proceso que hace la llamada. Devuelve una estructura %Task{}.
Para obtener el resultado de la ejecución, pasa la estructura %Task{} a Task.await/2. Esperará a que la tarea termine y devolverá su resultado. El segundo argumento es un tiempo de espera en milisegundos, que por defecto es 5000.
Ten en cuenta que, entre que se inicia la tarea y se espera por ella, el proceso que la inició no se bloquea y puede hacer otras operaciones.
Hay que esperar toda tarea iniciada con Task.async/1, porque enviará un mensaje al proceso que hace la llamada. Solo se puede llamar a Task.await/2 una vez por cada tarea.
start/start_link
Si quieres iniciar una tarea solo por sus efectos secundarios, usa Task.start/1 o Task.start_link/1. Task.start/1 iniciará una tarea que no está enlazada al proceso que hace la llamada, y Task.start_link/1 iniciará una tarea enlazada al proceso que hace la llamada. Ambas funciones devuelven una tupla {:ok, pid}.
Tu trabajo en Instruments of Texas con una calculadora RPN experimental continúa. Tu equipo ha construido algunos prototipos que deben someterse a una inspección exhaustiva, para elegir el mejor y producirlo en masa.
Quieres realizar dos tipos de comprobaciones.
Primero, una comprobación de confiabilidad que detectará entradas para las cuales la calculadora bajo inspección falla o no responde con la suficiente rapidez. Para aislar las fallas, los cálculos de cada entrada deben ejecutarse en un proceso separado. La vinculación y la captura de salidas en el proceso llamador se pueden usar para detectar si el cálculo terminó o falló.
Segundo, una comprobación de corrección que verificará si, para una entrada dada, el resultado devuelto por la calculadora es el esperado. Solo las calculadoras que ya pasaron la comprobación de confiabilidad se someterán a una comprobación de corrección, así que las fallas no son un problema. Sin embargo, las operaciones deben ejecutarse de forma concurrente para acelerar el proceso, lo que lo convierte en el caso de uso perfecto para tareas asíncronas.
Implementa la función RPNCalculatorInspection.start_reliability_check/2. Debe tomar 2 argumentos: una función (la calculadora) y una entrada para la calculadora. Debe devolver un mapa que contenga la entrada y el PID del proceso generado.
El proceso generado debe llamar a la función calculadora dada con la entrada dada. El proceso debe estar vinculado al proceso llamador.
RPNCalculatorInspection.start_reliability_check(fn _ -> 0 end, "2 3 +")
# => %{input: "2 3 +", pid: #PID<0.169.0>}
Implementa la función RPNCalculatorInspection.await_reliability_check_result/2. Debe tomar dos argumentos. El primer argumento es un mapa con la entrada de la comprobación de confiabilidad y el PID del proceso que ejecuta la comprobación de confiabilidad para esta entrada, tal como lo devuelve RPNCalculatorInspection.start_reliability_check/2. El segundo argumento es un mapa que sirve como acumulador de los resultados de las comprobaciones de confiabilidad con diferentes entradas.
La función debe esperar un mensaje de salida.
Si recibe un mensaje de salida ({:EXIT, from, reason}) con la razón :normal del mismo proceso que ejecuta la comprobación de confiabilidad, debe devolver el mapa de resultados con el valor :ok agregado bajo la clave input.
Si recibe un mensaje de salida con una razón diferente del mismo proceso que ejecuta la comprobación de confiabilidad, debe devolver el mapa de resultados con el valor :error agregado bajo la clave input.
Si no recibe ningún mensaje que cumpla con esos criterios en 100 ms, debe devolver el mapa de resultados con el valor :timeout agregado bajo la clave 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}
Implementa la función RPNCalculatorInspection.reliability_check/2. Debe tomar 2 argumentos: una función (la calculadora) y una lista de entradas para la calculadora.
Para cada entrada de la lista, debe iniciar la comprobación de confiabilidad en un nuevo proceso vinculado usando start_reliability_check/2. Luego, para cada proceso iniciado de esta manera, debe esperar sus resultados usando await_reliability_check_result/2.
Antes de iniciar cualquier proceso, la función necesita activar la captura de salidas en el proceso actual, para poder recibir mensajes de salida. Después, debe restablecer esta marca a su valor original.
La función debe devolver un mapa con los resultados de las comprobaciones de confiabilidad de todas las 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
# }
Implementa la función RPNCalculatorInspection.correctness_check/2. Debe tomar 2 argumentos: una función (la calculadora) y una lista de entradas para la calculadora.
Para cada entrada de la lista, debe iniciar una tarea asíncrona que llamará a la calculadora con la entrada dada. Luego, para cada tarea iniciada de esta manera, debe esperar sus resultados durante 100 ms.
fast_cheating_calculator = fn input -> 14 end
inputs = ["13 1 +", "50 2 *", "1000 2 /"]
RPNCalculatorInspection.correctness_check(fast_cheating_calculator, inputs)
# => [14, 14, 14]
Regístrate en Exercism para aprender y dominar Elixir con 58 conceptos168 ejercicios y mentoría humana real, todo gratis.