Stateful Goroutines in Elixir
Our example demonstrates how to manage state using processes in Elixir. This approach aligns with Elixir’s philosophy of using message passing for communication between concurrent processes.
defmodule StatefulProcess do
def start_link do
spawn_link(fn -> loop(%{}) end)
end
defp loop(state) do
receive do
{:read, key, caller} ->
send(caller, {:ok, Map.get(state, key)})
loop(state)
{:write, key, value, caller} ->
new_state = Map.put(state, key, value)
send(caller, :ok)
loop(new_state)
end
end
def read(pid, key) do
send(pid, {:read, key, self()})
receive do
{:ok, value} -> value
end
end
def write(pid, key, value) do
send(pid, {:write, key, value, self()})
receive do
:ok -> :ok
end
end
end
defmodule Main do
def run do
pid = StatefulProcess.start_link()
# Start 100 read processes
for _ <- 1..100 do
spawn(fn ->
for _ <- 1..1000 do
StatefulProcess.read(pid, :rand.uniform(5))
Process.sleep(1)
end
end)
end
# Start 10 write processes
for _ <- 1..10 do
spawn(fn ->
for _ <- 1..100 do
StatefulProcess.write(pid, :rand.uniform(5), :rand.uniform(100))
Process.sleep(1)
end
end)
end
# Let the processes run for a second
Process.sleep(1000)
# Print the final state
IO.puts "Final state:"
for i <- 1..5 do
value = StatefulProcess.read(pid, i)
IO.puts "#{i}: #{value}"
end
end
end
Main.run()
In this example, we use a single process to manage the state, which guarantees that the data is never corrupted with concurrent access. Other processes send messages to the state-owning process to read or write data.
The StatefulProcess
module defines the state-owning process. It uses a loop function that receives messages for reading and writing data. The read/2
and write/3
functions are used to send messages to this process and wait for the response.
In the Main
module, we start 100 processes that continuously read from the state, and 10 processes that write to the state. We use spawn/1
to create these processes, which is similar to starting goroutines in Go.
After letting the processes run for a second, we print out the final state.
Running this program will show the final state of the map after all the concurrent reads and writes:
$ elixir stateful_process.exs
Final state:
1: 42
2: 17
3: 89
4: 5
5: 63
This Elixir version demonstrates how to achieve similar functionality to the Go example, using Elixir’s built-in concurrency primitives. The approach aligns with Elixir’s philosophy of sharing data by sending messages between processes, rather than sharing memory directly.