Translate

lunes, 18 de noviembre de 2024

Elixir: Concurrencia Hecha Sencilla


Elixir, basado en la máquina virtual de Erlang (BEAM), utiliza el modelo de actores como su paradigma de concurrencia. Este modelo permite manejar múltiples procesos de manera eficiente y segura, lo que lo hace ideal para sistemas distribuidos y concurrentes.

El modelo de actores es un paradigma de concurrencia en el que las entidades llamadas actores:

  • Son unidades independientes de ejecución.
  • Tienen su propio estado y no comparten memoria con otros actores.
  • Se comunican mediante el envío de mensajes.

En Elixir, los procesos son implementaciones del modelo de actores y son extremadamente ligeros gracias a la eficiencia de la VM de Erlang.

En Elixir, los actores se implementan utilizando módulos como GenServer. Vamos a crear un ejemplo básico de un contador que incrementa su valor en respuesta a mensajes.

   defmodule Counter do

     use GenServer


     # Inicio del actor con un estado inicial

     def start_link(initial_value) do

       GenServer.start_link(__MODULE__, initial_value, name: __MODULE__)

     end


     # Callbacks

     def init(initial_value) do

       {:ok, initial_value}

     end


     # Manejar el mensaje para incrementar el contador

     def handle_call(:increment, _from, state) do

       {:reply, state + 1, state + 1}

     end


     def handle_call(:get, _from, state) do

       {:reply, state, state}

     end

   end


   # Iniciar el actor con un valor inicial de 0

   {:ok, _pid} = Counter.start_link(0)


   # Incrementar el contador

   Counter.call(:increment) # Devuelve 1

   Counter.call(:increment) # Devuelve 2


   # Obtener el valor actual

   Counter.call(:get) # Devuelve 2


Ahora crearemos múltiples actores que se comuniquen entre sí. Supongamos que tenemos un sistema donde un actor recopila datos y otro los procesa.


   defmodule DataCollector do

     use GenServer


     def start_link(processor_pid) do

       GenServer.start_link(__MODULE__, processor_pid, name: __MODULE__)

     end


     def init(processor_pid) do

       {:ok, processor_pid}

     end


     def handle_cast({:collect, data}, processor_pid) do

       send(processor_pid, {:process, data})

       {:noreply, processor_pid}

     end

   end

 

   defmodule DataProcessor do

     use GenServer


     def start_link(_) do

       GenServer.start_link(__MODULE__, [], name: __MODULE__)

     end


     def init(_) do

       {:ok, []}

     end


     def handle_info({:process, data}, state) do

       IO.puts("Procesando: #{data}")

       {:noreply, [data | state]}

     end

   end


   {:ok, processor_pid} = DataProcessor.start_link([])

   {:ok, collector_pid} = DataCollector.start_link(processor_pid)


   GenServer.cast(collector_pid, {:collect, "dato1"})

   GenServer.cast(collector_pid, {:collect, "dato2"})


DataCollector recopila datos y los envía a DataProcessor. DataProcessor procesa los datos recibidos y los guarda en su estado.


Como ventaja del modelo de actores tenemos:

Aislamiento Total: Los actores no comparten memoria, eliminando condiciones de carrera.

Escalabilidad: Los procesos son livianos y se ejecutan de manera concurrente.

Resiliencia: Si un actor falla, el sistema no se detiene; los supervisores pueden reiniciarlo.


El modelo de actores de Elixir proporciona una forma poderosa, segura y eficiente de manejar concurrencia. Al entender cómo implementar actores y supervisarlos, puedes construir sistemas robustos que escalen sin problemas.