In Elixir, all code runs inside processes. Elixir processes:
By default, a function will execute in the same process from which it was called. When you need to explicitly run a certain function in a new process, use spawn
:
spawn/1
accepts a function that it will execute directly.
spawn(fn -> 2 + 2 end)
# => #PID<0.125.0>
spawn/3
accepts a function that it will execute by the module name, the function name (as atom), and a list of arguments to pass to that function.
spawn(String, :split, ["hello there", " "])
# => #PID<0.113.0>
A process exits as soon as its function has finished executing.
You can check if a process is still alive (executing) with Process.alive?/1
:
pid = spawn(fn -> 2 + 2 end)
Process.alive?(pid)
# => false
Processes do not directly share information with one another. Processes send messages to share data. This concurrency pattern is called the Actor model.
Send messages to a process using send/2
.
send(pid, :hello)
send
does not check if the message was received nor if the recipient is still alive.A message can be of any type.
You can receive a message sent to the current process using receive/1
.
receive
waits until one message matching any given pattern is in the process's mailbox.
after
block._
clause in receive/1
to avoid running of out memory due to piled up unread messages.receive do
{:ping, sender_pid} -> send(sender_pid, :pong)
_ -> nil
after
5000 ->
{:error, "No message in 5 seconds"}
end
If you want to receive more than one message, you need to call receive/1
recursively. It is a common pattern to implement a recursive function, for example named loop
, that calls receive/1
, does something with the message, and then calls itself to wait for more messages. If you need to carry some state from one receive/1
call to another, you can do it by passing an argument to that loop
function.
def loop(state) do
receive do
:increment_by_one ->
loop(state + 1)
{:report_state, sender_pid} ->
send(sender_pid, state)
loop(state)
:stop ->
nil
_ ->
loop(state)
end
end
In practice, this approach is rarely used directly. Elixir offers concurrency abstractions, such as the Agent
module or a GenServer
behaviour, that both build on top of the receive loop. However, it is crucial to understand those basics to be able to efficiently use the abstractions.