Welcome to Part 2 of Elixir for the Lazy, Impatient and Busy! In Part 1, we took a whirlwind tour on Lists and Recursion. We are now going to do the same for Processes.
Elixir Processes ≠ OS Processes
As an Elixir programmer, you don’t have to mess with nasty operating system processes. Instead, we rely on the battle-tested Erlang Virtual Machine to handle all that heavy lifting for us.
In fact, you could happily spawn 5000 (and more!) processes and all your CPUs will happily light up. I always get a warm, fuzzy feeling knowing that I am getting my money’s worth of hardware.
Actor Concurrency Model
Before we get coding, you should know that Elixir/Erlang uses the Actor Concurrency Model.
Credits: http://learnyousomeerlang.com
Here’s my explanation:
1) Each Actor is a Process.
2) Each Process performs a specific task.
3) To tell a Process to do something, you need to send in a message. The Process can also send back another message.
4) The kinds of messages the process can act upon is specific to the process itself.
5) Other than that, processes do not share any information with other processes.
Creating Processes
Here’s some code that we want to run in a separate process:
defmodule Helloer do
def hola(msg) do
IO.puts "Hola! #{msg}"
end
end
IO.puts "Parent process is #{inspect self}"
IO.puts "Spawned process is #{inspect spawn(Helloer, :hola, ['Elixir is awesome'])}"
In order to do this, we use the spawn/3
function, like so:
spawn(Helloer, :hola, ["Elixir's awesome!"])
As you can guess, the first argument is the module name, followed by the function name (with a colon), then an array of arguments.
Running this code gives us:
Parent process is #PID<0.2.0>
Spawned process is #PID<0.37.0>
Hola! Elixir is awesome
Congratulations! You have just learnt how to spawn a process.
Before you continue, it would be good to browse the awesome documentation, type in spawn
in the search bar, and check out the different versions.
Come back when you are done.
Communicating with Processes
Simply spawn
-ing our processes is not much fun. How do we send messages?
Firstly, it is important to know that the return value of spawn
(and its close cousins, spawn_link
and spawn_monitor
) is the pid, also know as the process id.
The pid is the reference to the process. So you want to send messages to a process? You have to send it through the pid.
Sending messages with <-
and receive
-ing messages
The fun begins:
defmodule Helloer do
def hola do
receive do
{sender, msg} ->
sender <- "Received: '#{msg}'. Thank you!"
end
end
end
helloer_pid = spawn(Helloer, :hola, [])
helloer_pid <- {self, "Here's a message for you!"}
receive do
msg -> IO.puts msg
end
Running this code gives:
Received: 'Here's a message for you!'. Thank you!
Let’s break this code down:
We want to send a message to the hola
function via a process. To do that, we first need a reference to a process. And to do that, we have our friend, spawn
:
helloer_pid = spawn(Helloer, :hola, [])
Notice that this time, we are not passing anything into the argument list. We will be sending a message to it instead, using the <-
operator.
helloer_pid <- {self, "Here's a message for you!"}
The left of the arrow is the pid
that we want to send the message to. The right of the arrow is the message.
We are sending a tuple over to the helloer_pid
the message {self, "Here's a message for you!"}
.
What is self
? self
is a function that returns the pid of the calling process. Why do we need to pass self
? So we can send a message back to the calling process!
Before we get a little ahead of ourselves, we need to figure out how to receive messages. Recall now we are sending a message that looks like {self, "Here's a message for you!"}
to hola
.
Look at hola
and the receive
block. Notice the {sender, msg}
tuple. Because incoming message has the same pattern, hola
can happily handle this, and therefore execute the code on the right hand side of the ->
:
sender <- "Received: '#{msg}'. Thank you!"
Now hola
has received the message. The message contains a reference to the calling process. Using <-
, we can send another message back to the calling process!
But, recall that the calling process needs to handle incoming messages too:
receive do
msg -> IO.puts msg
end
In this case, the calling process is just expecting a single item, which is exactly what hola
is sending over with "Received: '#{msg}'. Thank you!"
Next time …
I hope your brain is hurting a little by now.
There’s more to processes I want to talk about. In particular, there’s a big limitation to the hola
function - It can only handle receiving one message!
We’ll see how we can use recursion to elegantly sidestep this limitation.
Thanks for reading!