Comfortable learning curve for Elixir — part 3

Processes and message passing

Gaspar Chilingarov
Learn Elixir
5 min readJul 10, 2017

--

This part introduces you to the processes and messages.

Great quote from learn you some erlang

If you were an actor in Erlang’s world, you would be a lonely person, sitting in a dark room with no window, waiting by your mailbox to get a message. Once you get a message, you react to it in a specific way: you pay the bills when receiving them, you respond to Birthday cards with a “Thank you” letter and you ignore the letters you can’t understand.

That’s exactly how Erlang and adherently Elixir works.

Processes

Each operation you execute in Elixir should run in some process. Some of the operations run in same process (sequential code), some operations belong to different processes and may run in parallel.

The very same code may run in parallel in many different processes. You may think of processes as object instances from OO paradigm. They are isolated, only they can change their internal state (all variables are private) and they provide limited interface to change it (by receiving and sending messages — which is similar to method invocation on object).

Processes can be spawned as easy as spawn(fn() -> :ok end) . Actually this process will be very short lived, as it will execute function and immediately terminate. In order to keep process running, you should use (infinite) recursion.

defmodule SampleProcess do
def lonely_loop(n\\0) do
Process.sleep 1_000
lonely_loop(n+1)
end
end
IO.inspect spawn(&SampleProcess.lonely_loop/0)
# which is the same as
# spawn(fn() -> SampleProcess.lonely_loop() end)

This code will just spawn process, which will stay there, alone, increment counter every second and never decide to die.

IO.inspect will print out PID of the process, which is unique identificator of the process while it lives.

Beware, tigers!

Beware, if you do not put Process.sleep there — it will eat all CPU time. And if you do not use tail recursion and run code like lonely_loop(n+1) + 1 it will eat all your RAM (and effectively bring your computer to halt). Try that is some virtual machine or in Linux bash/zsh using ulimit and see how it works.

bash -c 'ulimit -t 60 -d 1000000; elixir your_file.exs'

This limits execution time to 1 minute and memory to 1Gb.

Messages

Processes interact with each other only by sending messages. They cannot get each other’s state, they cannot modify each other’s state.

Sending messages is easy and this call does not block. It immediately runs code after it. As a message you can send any data structure in Elixir — integers, strings, tuples, lists, maps and everything else 😈

send pid, message

And for receiving use code below. It does block in receive until it gets a any message. When message is received it is assigned to variable message and code after -> is executed. So this block can execute arbitrary amount of time, depending on when message will arrive.

receive do
message -> IO.inspect message
end

Pattern matching works on messages and that is extremely useful

receive do
i when is_integer(i) -> IO.puts "integer #{i}"
{:count, n} -> IO.puts "got count #{n}"
end

First clause IO.puts “integer #{i}” will run only and only if message consists just on single integer. Second clause will run if message is a tuple with first element equal to atom :count .

Process linking

Processes may spawn another processes. And that may become problem when one of the processes exits for some reason — be that normal exit or crash.

There will be run-away processes, which take some resources in the system, most probably nobody knows their PIDs and cannot send any messages to them.

spawn_link solves this problem. It not only spawns a process, it also establishes bi-directional link between them. If one process exits — the other will exit automatically too. That ensures that all spawned processes either alive or they all exit together.

More about exceptions to that and how supervision works in next parts.

Exercise

Write a program to

  • create 100 processes and link them to each other (give PID of previous one)
  • then send message (number 0) to first one and make it increment it and pass to another process
  • print PID and number in each process and see how they execute sequentially

This should take about 1 hour.

Increase complexity

Make following changes to the code

  • make 10 processes
  • make processes linked in circle. You’ll need to tell last process PID of first process to make this happen. Try also another way — when you tell first process PID of the last process. In one case you may use just passing it as arguments, in other you will need to send extra message to process.
  • make message go in circle until counter passed in message reaches 100. You should see same PIDs receiving and sending messages to another process several times in a output.

Increase complexity up to 11

  • keep same amount of processes and link them in a circle.
  • now try to send 10 messages (label each of them) in a circle and see how they are passed in parallel. You should have 10 messages being passed from one process to another in parallel.
  • notice how console output from processes are mixed

What’s next?

In next part I will explain basics of GenServer. Subscribe to get updates when it’s out.

Also check out previous parts 1 2

Have questions? Write responses to this article 😺

About The Author

I’m Gaspar Chilingarov . I facilitate DevOps transition, help moving legacy applications to cloud and write high-performance Elixir apps. You can connect with me on Twitter, Facebook, LinkedIn and GitHub.

Found this post useful? Kindly tap the ❤ button below! :) Let’s spread word about Elixir.

--

--

Gaspar Chilingarov
Learn Elixir

I facilitate DevOps transition, help moving legacy applications to the cloud and write high-performance Elixir apps.