parent = self()
spawn(fn ->
send(parent, {:hello, "from a new process"})
end)
receive do
{:hello, text} -> IO.puts(text)
end
from a new process
A BEAM process is not an OS process or thread. It's a tiny isolated unit (a few KB) with its own heap, scheduled by the VM. Spawning millions is normal.
spawn/1 starts a function in a new process and
returns immediately. The new process runs
concurrently with the one that spawned it.
parent = self()
spawn(fn ->
send(parent, {:hello, "from a new process"})
end)
receive do
{:hello, text} -> IO.puts(text)
end
from a new process
Each process has its own memory. Rebinding a variable inside another process changes nothing here: there is no shared state to corrupt.
counter = 0
spawn(fn ->
counter = counter + 100
send(parent, {:child_saw, counter})
end)
receive do
{:child_saw, n} -> IO.puts("child saw #{n}, parent still has #{counter}")
end
child saw 100, parent still has 0
Processes are cheap enough to use one per task. Here 10,000 processes each compute a square and report back; we collect results in order by matching on the index.
n = 10_000
for i <- 1..n do
spawn(fn -> send(parent, {:square, i, i * i}) end)
end
squares =
for i <- 1..n do
receive do
{:square, ^i, sq} -> sq
end
end
IO.puts("sum of squares 1..#{n}: #{Enum.sum(squares)}")
sum of squares 1..10000: 333383335000
A process ends when its function returns. A crash
in a plain spawned process doesn't affect its
parent, which is the start of "let it crash".
pid = spawn(fn -> :ok end)
ref = Process.monitor(pid)
receive do
{:DOWN, ^ref, :process, ^pid, reason} -> IO.inspect(reason, label: "exited with")
end
IO.inspect(Process.alive?(pid), label: "alive?")
exited with: :normal
alive?: false
Process.info/2 peeks at a live process. Useful
when debugging in IEx; here we look at ourselves.
{:message_queue_len, len} = Process.info(self(), :message_queue_len)
IO.inspect(len, label: "messages waiting")
messages waiting: 0
Pitfall:
spawngives you no supervision and no crash notifications. In real code, start processes under a supervisor (Task.Supervisor,DynamicSupervisor) so they're restarted, shut down cleanly, and visible in observer tools.
Pitfall: sending a large data structure to another process copies it (except large binaries, which are reference-counted). Pass the data a process needs, not your whole state.