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.