Processes in Elixir
Processes
git stash
git pull
Erlang and Elixir processes are:
- lightweight (grow and shrink dynamically)
- with small memory footprint,
- fast to create and terminate,
- the scheduling overhead is low.
The theory behind a process is well explained in this blog post: https://www.erlang-solutions.com/blog/understanding-processes-for-elixir-developers/
Staring new process
Use function self/0
to determine what is PID (process ID) of current process.
self()
Get basic info about process using Erlang function process_info/0
.
self() |> :erlang.process_info()
Staring a new process can be done using spawn/1
or spawn/3
function.After spawning, new process is completly unrelated to the process was spawned by. There is no parent-child relation in oposition what you can see eg in Linux OS.
IO.inspect(self(), label: "I am parent process")
execute_fun = fn -> IO.inspect(self(), label: "I am child process") end
spawn(execute_fun)
defmodule PidPrinter do
def print_my_pid, do: IO.inspect(self(), label: "I am child process")
def print_my_pid(extra), do: IO.inspect({self(), extra}, label: "I am child process")
end
IO.inspect(self(), label: "I am parent process")
spawn(&PidPrinter.print_my_pid/0)
spawn(PidPrinter, :print_my_pid, ["XD"])
Naming processes
Processe can be named so that we do not have to remember their PIDs and we can reffer to them from anywhere in BEAM (Erlang VM).
execute_fun = fn ->
Process.register(self(), :krzys)
receive do
msg -> IO.inspect(msg, label: "Krzys received")
end
end
spawn(execute_fun)
## There is a race condition between process `:krzys` registering
## and current process looking up its pid based on it's name.
## Comment out next line to (possibly) see that.
Process.sleep(1000)
send(:krzys, "Hello")
Exercise 1 & 2
Visit gosocrative.com and enter room name ELIXIRSCHOOL
Comunication
Processes do not share memeory, they are independent and only communicate via messages. All comunication is based on asyncronous communication.
# sending message
send(self(), "Hello")
# receiving message
receive do
msg -> IO.inspect(msg, label: "I have received")
end
# self() |> :erlang.process_info(:message_queue_len) |> IO.inspect()
# send(self(), {:question, "How are you?"})
# self() |> :erlang.process_info(:message_queue_len) |> IO.inspect()
# receive do
# {:greetings, content} -> IO.inspect(content, label: "I have received")
# end
# ## This will neve happen
# self() |> :erlang.process_info(:message_queue_len) |> IO.inspect()
# ## comment out after showing
After the previous block was executed there are some trash messages left in our main process. We can flush them by running following function:
flush_fun = fn fun ->
receive do
msg ->
IO.inspect(msg)
fun.(fun)
after
0 ->
IO.inspect("flushing is done")
:ok
end
end
flush_fun.(flush_fun)
When selectivly waiting the message may never arrive, therefore timeout part is added to the receive block:
send(self(), {:question, "How are you?"})
receive do
{:greetings, content} -> IO.inspect(content, label: "I have received")
after
# timeout is in miliseconds
3000 -> IO.inspect(:timeout, label: "I have received")
end
Exercise 3 & 4
Visit gosocrative.com and enter room name ELIXIRSCHOOL
Communication cont.
We can use this block to implement our own Process.sleep/1
function. And suprrise this is how it is done in the OTP itself: https://github.com/erlang/otp/blob/896518d196cd81ba26cb084827957a93b90220fb/lib/stdlib/src/timer.erl#L237
sleep_fun = fn time ->
receive do
after
time -> :ok
end
end
sleep_fun.(3000)
We can use selective receives to ensure that messages are consumed in particular order:
send(self(), {:question, "How are you?"})
Process.sleep(3000)
send(self(), {:greetings, "Hello"})
receive do
{:greetings, content} ->
IO.inspect(content, label: "I have received")
end
receive do
{:question, question} ->
IO.inspect(question, label: "I have received")
end
# receive do
# {:greetings, content} ->
# IO.inspect(content, label: "I have received")
# {:question, question} ->
# IO.inspect(question, label: "I have received")
# end
# flush_fun.(flush_fun)
Exercise 5
Visit gosocrative.com and enter room name ELIXIRSCHOOL
Monitors
Monitor is a way of observing how some other is process doing. When process A monitors process B, and process B crushes, process A gets notified by receiving a message: {'DOWN', ref, :process, pidB, reason}
. Monitors are unidrectional which means that if A monitors B, B does not know it is monitored.
## Cleanup
for proc <- [A, B, C] do
proc
|> Process.whereis()
|> (fn
nil -> :ok
pid -> Process.exit(pid, :kill)
end).()
end
pidB =
spawn(fn ->
Process.register(self(), B)
receive do
:crash -> 1 = 2
:die_normally -> :ok
end
end)
Process.alive?(pidB)
Process can die in either normal or unnormal way. Normal is when it runs out of code do execute, and unnormal is when is crashses, eg by trying to match 2
to value 1
.
## Current process is emulating A process
flush_fun.(flush_fun)
Process.monitor(B)
send(B, :crash)
receive do
msg -> IO.inspect(msg, label: "A received")
after
0 -> :ok
end
# Process.monitor(B)
# send(B, :die_normally)
# receive do
# msg -> IO.inspect(msg, label: "A received")
# after 1000 -> :ok
# end
Excersise 6
Visit gosocrative.com and enter room name ELIXIRSCHOOL
Links
When two processes can be linked to each other. If one of the participants of a link terminates, it will send an exit signal to the other participant. The exit signal will contain the exit reason of the terminated participant. Links are bidirectional.
IO.inspect(self(), label: "I am")
a =
spawn(fn ->
Process.register(self(), A)
IO.inspect({A, self()}, label: "I am")
receive do
:crash -> 1 = 2
:die_normally -> :ok
end
end)
Process.sleep(100)
b =
spawn(fn ->
Process.register(self(), B)
IO.inspect({B, self()}, label: "I am")
Process.link(a)
receive do
_ -> :ok
end
end)
# Process.sleep(100)
# c = spawn(fn ->
# Process.register(self(), C)
# IO.inspect({C, self()}, label: "I am")
#
# Process.link(b)
# receive do
# _ -> :ok
# end
# end)
Process.sleep(100)
send(A, :crash)
# send(A, :die_normally)
Process.sleep(100)
IO.inspect(Process.alive?(a), label: "Final A info")
IO.inspect(Process.alive?(b), label: "Final B info")
# IO.inspect(Process.alive?(c), label: "Final C info")
Excersise 7
Visit gosocrative.com and enter room name ELIXIRSCHOOL
There is an option set a special process flag to trap exits. When trap_exit
process flag is set to true, instread of receiving exit signal a process receives a message:
## Cleanup
for proc <- [A, B] do
proc
|> Process.whereis()
|> (fn
nil -> :ok
pid -> Process.exit(pid, :kill)
end).()
end
IO.inspect(self(), label: "I am")
a =
spawn(fn ->
Process.register(self(), A)
IO.inspect({A, self()}, label: "I am")
receive do
:crash -> 1 = 2
:die_normally -> :ok
end
end)
Process.sleep(100)
b =
spawn(fn ->
Process.register(self(), B)
Process.flag(:trap_exit, true)
IO.inspect({B, self()}, label: "I am")
A
|> Process.whereis()
|> Process.link()
receive do
msg -> IO.inspect(msg, label: "The trap exit messge")
end
receive do
_ -> :ok
end
end)
Process.sleep(100)
send(A, :crash)
Process.sleep(100)
IO.inspect(Process.alive?(a), label: "Final A info")
IO.inspect(Process.alive?(b), label: "Final B info")
Excersise 8 & 9 & 10
Visit gosocrative.com and enter room name ELIXIRSCHOOL
My email
klemens.lukaszczyk@erlang-solutions.com