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.