Message Passing

Processes communicate with each other using message passing. Messages
are sent using the Pid ! Message
construct, where Pid is a valid process
identifier and Message is a value from
any Erlang data type (see Figure 4-2).

Figure 4-2. Message passing

Each Erlang process has a mailbox in which incoming messages
are stored. When a message is sent, it is copied from the sending process
into the recipient’s mailbox for retrieval. Messages are stored in the
mailbox in the order in which they are delivered. If two messages are sent
from one process to another, the messages are guaranteed to be received in
the same order in which they are sent. This guarantee is not extended to
messages sent from different processes, however, and in this case the
ordering is VM-dependent.

Sending a message will never fail; so if you
try sending a message to a nonexistent process, it is thrown away without
generating an error. Finally, message passing is
asynchronous: a sending process will not be suspended
after sending a message; it will instead immediately continue executing
the next expression in its code.

To test sending messages in the shell, let’s use theself/0 BIF, which
returns the pid of the process in which it is evaluated. The Erlang shell is nothing other than an Erlang process in a read-evaluate-print loop, waiting for
you to type in an expression. When you terminate an expression followed by a
full stop (.) and press Enter, the shell evaluates what you typed in and
prints out a result. Since the shell is an Erlang process, there is
nothing stopping us from sending messages to it. To retrieve and display
all the messages sent to the shell process, and therefore currently held
in the process mailbox, you can use the shell commandflush/0, which also has
the effect of removing (or flushing) those messages
from the mailbox:

What is happening in the preceding example? In command 1, the BIF
self() returns a pid, which in the
shell is bound to the variable Pid and
displayed as <0.30.0>. In
commands 2 and 3 you see the message being sent to the Pid, and then flushed from the mailbox, using
the flush() command in the
shell.

You cannot type pids directly in a module or in the shell, as in
both cases, they result in a syntax error; this is shown for the shell in
command 4. You need either to bind the process identifiers to a variable
when BIFs such as self and spawn return them, or generate a pid using
thepid/3 shell function, as
shown in command 5 and used in command 6. The flush() in command 7 shows that the message
indeed went to the shell process.

Pid ! Message is a valid Erlang
expression, and as with all valid expressions in Erlang, it has to return
a value. The value, in this case, is the message
sent. So if, for example, you need to send the same message to
many processes, you can write either a sequence of message sends, such as
Pid1!Msg,Pid2!Msg,Pid3!Msg, or a single
expression, such as Pid3!Pid2!Pid1!Message, which is equivalent to
writing Pid3!(Pid2!(Pid1!Message)),
where Pid1!Message returns the message
to send to Pid2, which in turn returns
the message to be sent to Pid3.

As we already said, sending messages to nonexistent processes will
always succeed. To test this, let’s make the shell process crash with an
illegal operation. Crashing is the same as an abnormal process
termination, something that is considered normal in Erlang, in the sense
that Erlang provides mechanisms to deal with it. We will cover abnormal
process terminations in more detail in the next chapter, so until then, do
not get alarmed. Making the shell crash will automatically result in a new
shell process—in this example with pid <0.38.0>—being spawned by the runtime
system.

With this in mind, we locate the shell pid, make the shell process
terminate, and then send a message to it. Based on the semantics of
message passing, this will result in the message being thrown away:

The reason that message passing and spawn always succeed, even if the recipient
process does not exist or the spawned process crashes on creation, has to
do with process dependencies, or rather, their
deliberate lack of dependencies. We say that process A depends
on process B when the fact of B terminating can prevent A from
functioning correctly.

Process dependencies are very important and will often influence your design. In
massively concurrent systems, you do not want processes to depend on each
other unless explicitly specified, and in such cases, you want to have as
few dependencies as possible. To give a concrete example of this, imagine
an IM server concurrently handling thousands of messages being exchanged
by its users. Each message is handled by a process spawned for that
particular function. If, due to a bug, one of these processes terminates,
you would lose that particular message. Ensuring a lack of dependency
between this process and the processes handling all the other messages
guarantees that these messages are safely processed and delivered to their
recipients regardless of the bug.

The best content for your career. Discover unlimited learning
on demand for around $1/day.