Part 1 of this series looked at how to create processes and how to share memory among them. This article discusses how to deal with largely independent processes that communicate by exchanging messages.
Message-passing processes are generally much easier to reason about than those that rely on shared memory and locking. The messy parts of the low-level IPC are generally hidden from the developer, and you just have to worry about making sure that your processes are sufficiently independent.
Processes communicating in this way can also be used for security; if you have to run a part of your program as root (for example, to bind to a low-numbered socket, or to run programs as other users), you can isolate this part in one process, which is easier to audit, and run the rest at a lower privilege level.
Message-passing processes typically have a run loop that involves a receive > process > send cycle. They take a message from the queue, process it, and then send a result. In this respect, the initial message can be thought of as an asynchronous function call.
Some function calls are already implemented like this under the hood. When you write some data to disk, typically the function you call will copy the data into a kernel buffer and the kernel will asynchronously write the data to disk. Often, it’s a good idea to do the same with your code—create an interface to the other process that looks like a set of functions. Then you can change the mechanism used for sending the messages, without having to modify your code in lots of places.