The free lunch is over.
Eight years ago, Herb Sutter wrote a very compelling article, titled The Free Lunch is Over detailing how the major processors and architectures were turning to hyperthreading and multicore.
The majority of developers at the time, including myself, were all writing software designed to run on single core machines. What the news meant to us was that, if we actually wanted our software to run faster in the upcoming years, we would have to effectively change the way we write software!
At the time (and still today), many developers were writing object-oriented software, which focus on encapsulating state and changing this state. When it comes to multi-core software, mutating this state becomes trickier because two different cores may try to mutate the same state at the same time. Multi-core software requires concurrency and concurrency introduces the need for coordination.
We have all been there. Organizing a whole party on your own can be a lot of work. Organizing a party in a group is much more fun but without coordination it becomes a recipe for disaster, as we'd find out on the day of the party that nobody sent the invites because everyone assumed someone else has done so. The tools we use for coordination also directly affect the fun while organizing and the success of the party.
Multi-core programming is not much different. Concurrency can be a lot of fun, as long as we have the proper tools available to us. The issue is that, historically, those proper tools were not available in mainstream languages.
Today, eight years after the article, many things have changed. First off, hyperthreading and multi-core have indeed become the reality. Even the cheapest CPUs today run on a multicore architecture, providing at least two cores. In fact, I am writing this article in a computer running on a Intel® Core™ i5, that has four cores, each of them with 2.80GHz. Fun fact: the 2.80GHz clock rate was reached commercially by Intel® back in 2002 with Pentium® 4.
Second of all, we have seen a great growth in the commercial use and search for functional programming languages, like F#, Scala, Clojure, Haskell and more. And the reason for such is because functional languages provide a better foundation for thinking concurrently and the proper tools for writing concurrent software.
Elixir and Concurrency
When writing Elixir software, we organize our code into tiny processes, let's see an example:
After the process is created, we send it a message labelled , also passing , which is the current process, in the message. Finally, the same process that sent the waits for a message back or prints a failure message if such message does not arrive in 1 second (1000 miliseconds).
Although a very simple example, this code snippet reflects a lot on how Elixir code works and runs:
- Your code is organized into processes and creating a process is very cheap;
- Those processes do not share any state, instead they communicate via messages;
- As processes do not share any state, they run concurrently and use blocks for receiving messages (coordination);
- Those processes are isolated. If something goes wrong in a particular process, other processes will continue running;
- Processes can fail as well as the communication in between them. Using inside a block allows us to react to those scenarios;
By not sharing any state and communicating via messages, processes are the building block for concurrency in Elixir, bringing the joy for building concurrent systems. This is so spread throughout the language and the ecosytem that it can be verified in your first experience with Elixir, which is installing Elixir itself. When installing Elixir, you should see all cores on your machines being used, the same for running the Elixir language test suite.
This abstraction deeply affects how we write software and none of this is novel. All those features are provided by the Erlang Virtual Machine, and both Erlang and Elixir languages leverage its power.
The Erlang Virtual Machine
Erlang is a language and a Virtual Machine developed by Ericsson). The first version developed internally at Ericsson in 1986 was designed with the aim of improving the development of telephony applications which have a very specific set of requirements:
Fault-tolerant: a telephony system should continue operating properly even in the event of the failure of some of its components;
Non-stop: such applications should work non-stop and avoid downtimes. In fact, it is possible to perform hot-code swaps, which allows developers to upgrade code in production without shutting off the system;
Distributed: different machines in the same telephone system should be able to easily exchange information in between them;
Let's see how those three characteristics maps to the processes we have just discussed:
Fault-tolerant: our code is built with processes that are isolated and if a process fails, other processes in the system can continue running. Furthermore, we often define supervisors, which is a particular kind of processes that watch over other processes and restart them when something goes wrong. In fact, "let it crash" is a common philosophy when writing software for the Erlang VM, since supervisors are able to restart processes so they can go back to a known, working state;
Non-stop: processes do not share any state, instead each process has its own state and information is exchanged via message passing. This means that, if a process is acting poorly, we can update its code in production (and its state), without affecting the remaining of the system as long as we keep sending and receiving such messages properly. If instead processes shared state, such code upgrades would be much harder because we would need to consider how changing the state would affect all other components in the system;
Distributed: because the system is designed around message passing, the Erlang VM abstracts the message passing in a such a way that it doesn't matter if the recipient of the message is in the same machine or in another node in the same network;
In other words, while I have lured you into this article with promises of concurrency, we are actually getting much more than concurrency. We are getting a whole framework for building fault-tolerant, concurrent and distributed applications! And all this made available by the Erlang VM.
Everything I have talked so far applies to all languages that run on the Erlang VM. So, why Elixir? We will explore what Elixir brings to the Erlang VM and discuss Elixir's goals in the second part of this article.