पाठ 10 / 25

Processes and Message Passing

Spawn processes, send and receive messages, and link or monitor them.

The actor model on the BEAM

A BEAM process is an isolated unit of execution with its own heap and mailbox; it is not an OS thread, and creating one takes microseconds and a few kilobytes. spawn/1 starts a process running a function and returns its PID. Processes communicate only by messages: send(pid, message) is asynchronous and copies the message into the receiver's mailbox, and receive pattern-matches messages from the mailbox, optionally with an after timeout. Because nothing is shared, there are no locks or data races. A process keeps state by calling itself recursively with new arguments in a receive loop. Links (spawn_link) tie two processes together so that if one crashes, the other receives an exit signal and normally crashes too, which supervisors use to detect failures. Monitors (Process.monitor) are one-way: the watcher receives a :DOWN message instead of crashing. Processes can be registered under a name. The BEAM schedules processes preemptively, so one busy process cannot freeze the others, which keeps latency predictable.

Processes and mailboxes

Each process has a private mailbox; messages are copied, never shared.

Three circles each with a small envelope tray, with envelopes moving between them along arrows.
Figure 4.1 — Message passing between isolated processes.

A stateful counter process

A recursive receive loop holds state; links and monitors report failures.

defmodule Counter do
  def start(initial \\ 0), do: spawn(fn -> loop(initial) end)

  defp loop(count) do
    receive do
      {:increment, by} ->
        loop(count + by)                      # new state via recursion

      {:get, caller} ->
        send(caller, {:count, count})
        loop(count)

      :stop ->
        :ok                                   # returning ends the process
    end
  end
end

pid = Counter.start()
send(pid, {:increment, 5})
send(pid, {:increment, 2})
send(pid, {:get, self()})

receive do
  {:count, value} -> IO.puts("count = #{value}")    # count = 7
after
  1_000 -> IO.puts("no reply")
end

# monitoring: get a :DOWN message instead of crashing
worker = spawn(fn -> raise "boom" end)
ref = Process.monitor(worker)

receive do
  {:DOWN, ^ref, :process, ^worker, reason} -> IO.puts("worker died: #{inspect(reason)}")
end

Always plan for unmatched messages

Messages that no receive clause matches stay in the mailbox forever, slowing every future receive. GenServer handles this for you; in hand-written loops, add a catch-all clause or use timeouts.

त्वरित जाँच: How do BEAM processes share data?

  • Through shared memory and locks
  • Through global variables
  • They do not share memory; they communicate by copying messages into mailboxes
  • Through files only
Answer

They do not share memory; they communicate by copying messages into mailboxes — Isolation plus message passing eliminates data races.