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 Elixir que ejecuta cientos de procesos, pero también una que no crea procesos nuevos explícitamente en absoluto, sobre todo si es una biblioteca.

Crear procesos

De forma predeterminada, una función se ejecuta en el mismo proceso desde el que se llamó. Cuando necesites ejecutar explícitamente una función concreta 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á indicando 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: Módulo, Función, Argumentos.
  • Un proceso finaliza 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 modelo de actores.

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

    send(pid, :hello)
    
    • El mensaje acaba en el buzón del destinatario en el mismo orden en que se envían.
    • send no comprueba si el mensaje se ha 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.

    • Debes usar la coincidencia de patrones con los mensajes.
    • receive espera hasta que haya en el buzón del proceso un mensaje que coincida con alguno de los patrones dados.
      • De forma predeterminada, espera indefinidamente, pero se le puede dar un tiempo de espera con un bloque after.
    • Los mensajes leídos se eliminan del buzón del proceso. Los mensajes no leídos permanecerán ahí indefinidamente.
      • Escribe siempre una cláusula _ que abarque 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, tienes que llamar a receive/1 de forma recursiva. Es un patrón habitual 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 arrastrar algo de 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 un comportamiento GenServer, que se construyen sobre el bucle de recepción. Sin embargo, es fundamental entender esos conceptos básicos para poder usar las abstracciones de forma eficiente.

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

Aprende Procesos

La práctica está bloqueada

Desbloquea 4 ejercicios más para practicar Procesos