Processes and Messaging
Elixir processes, spawn, send/receive, links, monitors, and the actor model
Elixir runs on the BEAM (Erlang VM) and uses lightweight processes (not OS threads) for concurrency. Processes communicate by sending messages.
Spawning processes
# Spawn a process
pid = spawn(fn -> IO.puts("Hello from process") end)
# Spawn with a registered name
pid = spawn(fn -> loop() end)
Process.register(pid, :my_worker)
# Spawn linking (dies together)
spawn_link(fn -> raise "boom" end)
# Spawn monitoring (get notified on exit)
spawn_monitor(fn -> raise "boom" end)Sending and receiving messages
# Send a message
send(pid, {:hello, "world"})
send(:my_worker, {:hello, "world"})
# Receive a message (blocks until a match)
receive do
{:hello, name} -> IO.puts("Hello #{name}")
{:error, reason} -> IO.puts("Error: #{reason}")
after
5000 -> IO.puts("Timed out")
endFlush mailbox
# View and remove all messages in the current process mailbox
flush()Process info
Process.alive?(pid) # => true | false
Process.info(pid, :message_queue_len) # => {:message_queue_len, 0}
Process.info(pid, :links) # => {:links, []}
self() # => current process PIDLinks and monitors
Links (bidirectional)
If a linked process crashes, the other process also crashes.
spawn_link(fn -> raise "boom" end) # kills both processes
Process.link(pid) # link to an existing process
Process.unlink(pid) # remove linkMonitors (unidirectional)
Get a message when a process exits, without crashing.
ref = Process.monitor(pid)
# When pid exits, you receive:
# {:DOWN, ref, :process, pid, reason}
Process.demonitor(ref) # stop monitoring
Process.demonitor(ref, [:flush]) # stop + flush existing DOWN messagesState with processes
A common pattern: a process that maintains state by recursively looping and receiving messages.
defmodule Counter do
def start(initial \\ 0) do
spawn(fn -> loop(initial) end)
end
def increment(pid), do: send(pid, :inc)
def decrement(pid), do: send(pid, :dec)
def get(pid), do: send(pid, {:get, self()})
defp loop(count) do
receive do
:inc -> loop(count + 1)
:dec -> loop(count - 1)
{:get, from} ->
send(from, {:count, count})
loop(count)
end
end
end
pid = Counter.start()
Counter.increment(pid)
Counter.increment(pid)
Counter.get(pid)
# receive do {:count, n} -> n end => 2Process dictionary
Each process has a local key-value store. Use sparingly — prefer passing state explicitly.
Process.put(:key, "value")
Process.get(:key) # => "value"
Process.get_keys() # => [:key]
Process.delete(:key)Exiting and signals
# Normal exit
Process.exit(pid, :normal) # process continues (doesn't kill)
# Kill (untrappable)
Process.exit(pid, :kill) # process dies immediately
# Abnormal exit
Process.exit(pid, :shutdown) # process dies
# Trap exits in a process
Process.flag(:trap_exit, true)
# Now you receive {:EXIT, pid, reason} messages instead of crashing