The focus of this post is to explain the communication between processes in elixir in a simple way. Here you can read the portuguese version of this article. When you've finished reading, you'll have a basic understanding of how to create processes and how they send and receive messages.
How to create processes
A process is something that can do things in isolation and concurrently. Well... this is pretty vague. Let's try a more concrete example. Let's say you are a mouse process. You can do many things that mice do, like hiding or eating cheese for example. You are independent, you don't need other people to do what you want and they don't interfere with your activities. That is, you do it in isolation. In a mice contest to see who eats the most cheese, everyone could do it concurrently and in parallel. Everyone eats at the same time and not one at a time.
Now that we have an idea of what a process is, let's imagine that you want to make a new friend, that is, another process. So let's create the cat process. We can do this with the spawn
function.
The spawn function creates a new process and performs a function on it. It has two arities: spawn/1
takes a function and spawn/3
takes a module, a function as an atom and a list of parameters. Here we are going to use the second form because we are going to define Cat
in a module. The create
function just prints a message. You can put the code below in a file called mouse.exs
and run it with elixir mouse.exs
. If you do this you will see the commented result.
# mouse.exs
defmodule Cat do
def create do
IO.puts("You create cat")
end
end
# Here you create a cat
spawn(Cat, :create, [])
# Result:
# "You create cat"
Communication between processes
After creating a new process, your friend the cat, you want to talk to him. But remember that processes are isolated. Imagine that everyone lives in their own home with social distance. So how to communicate with him? Millions of years ago people used a form of communication called mail. Processes communicate in a similar way. So to talk to your friend you will send a message like that.
Sending and receiving messages
We send a message with the send/2
function. We pass to this function two things: the receiver and the message. But how will our code know how to send the message to the cat process? Thankfully, the spawn
function returns an identifier of the process created for us, as if it were its address. So let's save the spawn
return in the variable cat
.
cat = spawn(Cat, :create, [])
The cat needs to know how to receive this message. For this we defined the receive
block. Here's how it works: the cat will look inside the mailbox and decide what is important. That is, for which messages he wants to do something.
# mouse.exs
defmodule Cat do
def create do
receive do
:how_is_it_going -> IO.puts("Good!")
end
end
end
# Here you create a cat
cat = spawn(Cat, :create, [])
# Send the message
send(cat, :how_is_it_going)
# Result:
# "Good!"
Answering back
Our code has a problem. Imagine IO.puts("Good!")
as if the cat had said that. But you are in your own homes, remember? So you didn't hear his answer. Because processes communicate with messages, he would have to send you a message back.
Glad you already know how to send a message. Just make the cat respond with send(mouse, :good)
and it's all right! However, how will the cat know that the message came from the mouse? To do this, let's say in the message who the sender
was. We have to change the cat's receive
to care for messages that follow the pattern {:how_is_it_going, sender}
. So he can reply to the right person with send(sender, :good)
. It looks like this:
receive do
{:how_is_it_going, sender} -> send(sender, :good)
end
Now the mouse needs to identify itself to send the message send(cat, {:how_is_it_going, mouse})
. Remember that the mouse is the current process. To identify the current process in the code we use the self/0
function. It's like we're getting our own address. So we save its return in a variable mouse = self()
.
# This is you
mouse = self()
# Send the message
send(cat, {:how_is_it_going, mouse})
Almost everything is ready. We just need to look in the mouse's mailbox for the message the cat sent and decide what to do. The entire code would look like this:
# mouse.exs
defmodule Cat do
def create do
receive do
{:how_is_it_going, sender} -> send(sender, :good)
end
end
end
# This is you
mouse = self()
# Here you create a cat
cat = spawn(Cat, :create, [])
# Send the message
send(cat, {:how_is_it_going, mouse})
# Look in the mouse's mailbox
receive do
:good -> IO.puts("Nice!")
end
# Result:
# "Nice!"
Temporary Process
You can check whether a process is alive with the Process.alive?/1
function. Let's run the same code above but see when the cat process is alive by adding IO.puts("Is the cat alive? #{Process.alive?(cat)}")
.
# mouse.exs
defmodule Cat do
def create do
receive do
{:how_is_it_going, sender} -> send(sender, :good)
end
end
end
# This is you
mouse = self()
# Here you create a cat
cat = spawn(Cat, :create, [])
IO.puts("Is the cat alive? #{Process.alive?(cat)}")
# Send the message
send(cat, {:how_is_it_going, mouse})
# Look in the mouse's mailbox
receive do
:good -> IO.puts("Nice!")
end
IO.puts("Is the cat alive? #{Process.alive?(cat)}")
# Result:
# Is the cat alive? true
# Nice!
# Is the cat alive? false
We can see that the process was alive, but after answering it died. Why did this happen?
To understand what happened let's look at the create
function. Here we defined a receive
block. Since the mouse received the message back, communication is correct. But what happens after the cat responds? Nothing else happens in the cat process. As such, it no longer needs to exist and the process dies!
def create do
receive do
{:how_is_it_going, sender} -> send(sender, :good)
end
end
Permanent Process
To keep the process alive, let's make sure it doesn't respond to just one message. It will keep checking messages and replying. We can do this by defining the check_messages/0
function and passing the receive
block to it.
def create do
check_messages()
end
def check_messages do
receive do
{:how_is_it_going, sender} -> send(sender, :good)
end
check_messages()
end
So the only thing the create function does is to tell the cat to check the messages. Now look at the last line of check_messages/0
. It calls itself! That is, whenever it ends, it goes back to the beginning. It's a recursive function. That way the cat checks messages forever and doesn't die. The whole code looks like this:
# mouse.exs
defmodule Cat do
def create do
check_messages()
end
def check_messages do
receive do
{:how_is_it_going, sender} -> send(sender, :good)
end
check_messages()
end
end
# This is you
mouse = self()
# Here you create a cat
cat = spawn(Cat, :create, [])
IO.puts("Is the cat alive? #{Process.alive?(cat)}")
# Send the message
send(cat, {:how_is_it_going, mouse})
# Look in the mouse's mailbox
receive do
:good -> IO.puts("Nice!")
end
IO.puts("Is the cat alive? #{Process.alive?(cat)}")
# Result:
# Is the cat alive? true
# Nice!
# Is the cat alive? true
Conclusion
Here you learned the basics of how to create processes with spawn
. How they send messages with send/2
and receive messages with the receive
block. You saw a little bit about how a process can die and how to keep it alive. And the best of all, cats and mice can be friends after all!
Top comments (0)