Pr

Procesos en Elixir

5 ejercicios

Acerca de Procesos

En Elixir, todo el código se ejecuta dentro de procesos. Los procesos de Elixir:

  • No deben confundirse con los procesos del sistema.
  • Son ligeros.
  • Tienen casos de uso específicos. Pueden:
  • Es normal tener una aplicación de Elixir que ejecuta cientos de procesos, pero también una que no crea procesos nuevos de forma explícita, sobre todo si es una biblioteca.

Crear procesos

De forma predeterminada, una función se ejecuta en el mismo proceso desde el cual fue llamada. Cuando necesitas ejecutar explícitamente una función determinada en un proceso nuevo, usa spawn:

  • spawn/1 acepta una función que ejecutará directamente.

    spawn(fn -> 2 + 2 end)
    # => #PID<0.125.0>
    
  • spawn/3 acepta una función que ejecutará por el nombre del módulo, el nombre de la función (como átomo) y una lista de argumentos que se pasan a esa función.

    spawn(String, :split, ["hello there", " "])
    # => #PID<0.113.0>
    
    • A este trío de datos se le suele llamar MFA: Module, Function, Arguments.
  • Un proceso termina en cuanto su función ha terminado de ejecutarse.

  • Puedes comprobar si un proceso sigue vivo (ejecutándose) con Process.alive?/1:

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

Mensajes

Los procesos no comparten información directamente entre sí. Los procesos envían mensajes para compartir datos. Este patrón de concurrencia se llama el modelo de actores.

  • Envía mensajes a un proceso con send/2.

    send(pid, :hello)
    
    • El mensaje termina en el buzón del destinatario en el orden en que se envían.
    • send no comprueba si el mensaje fue recibido ni si el destinatario sigue vivo.
  • Un mensaje puede ser de cualquier tipo.

  • Puedes recibir un mensaje enviado al proceso actual con receive/1.

    • Necesitas hacer coincidencia de patrones con los mensajes.
    • receive espera hasta que un mensaje que coincida con alguno de los patrones dados esté en el buzón del proceso.
      • De forma predeterminada, espera indefinidamente, pero se le puede dar un tiempo límite con un bloque after.
    • Los mensajes leídos se eliminan del buzón del proceso. Los mensajes no leídos se quedan ahí indefinidamente.
      • Escribe siempre una cláusula _ que capture todo en receive/1 para evitar quedarte sin memoria por la acumulación de mensajes no leídos.
    receive do
      {:ping, sender_pid} -> send(sender_pid, :pong)
      _ -> nil
    after
      5000 ->
        {:error, "No message in 5 seconds"}
    end
    

Bucle de recepción

Si quieres recibir más de un mensaje, necesitas llamar a receive/1 de forma recursiva. Es un patrón común implementar una función recursiva, por ejemplo llamada loop, que llama a receive/1, hace algo con el mensaje y luego se llama a sí misma para esperar más mensajes. Si necesitas llevar algún estado de una llamada a receive/1 a otra, puedes hacerlo pasando un argumento a esa función 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

En la práctica, este enfoque rara vez se usa directamente. Elixir ofrece abstracciones de concurrencia, como el módulo Agent o el comportamiento GenServer, que se construyen sobre el bucle de recepción. Sin embargo, es fundamental entender esas bases para poder usar las abstracciones de forma eficiente.

Editar en GitHub El enlace se abre en una ventana o pestaña nueva

Aprende Procesos

La práctica está bloqueada

Desbloquea 4 ejercicios más para practicar Procesos