A Gentle Intro to Processes in Elixir
Tags: Elixirlang, JavaScript, Elixir

Suppose that, like me, you’ve spent a long time writing JavaScript. Then you enter the exciting realm of Elixir and begin to discover the many wonders of working with processes. I offer some basic questions answered.

“Aspens II”, oil on canvas, by Feather Knee

JavaScript is a single-threaded language. It may appear at times as though things are happening too fast for this to be so. A lot of clever compiler optimizations have resulted in great speed and weird things like variable declaration hoisting. These might make it appear as though commands are not being executed in order, often a side-effect of parallelism. There is no parallelism, though. A scheduler metes work out to the call stack in the order it was received. Each request is popped off the stack in order, with a minimum of funny business. The one exception to this is anything that happens in setTimeout or setInterval as this is a separate call stack.

If you write a basic module in Elixir it will execute in a single process too. Functions are called in the order they are written. The first function with a matching arity will execute, which is why the most generic overloaded function should always appear last.

In Elixir, however, rather than have one main process that runs through all of the commands in order there can be multiple processes running simultaneously.

Take this snippet. When the timer goes off the wakeup message appears.

What if this function were wrapped inside of a call to spawn ?

Which one would return the message more quickly?

If you guessed the second one, you are right. In the first example we will only see the message after the timer has finished. In the 2nd we see the message right away.

Spawned processes will not cause any waiting time before the next command can be executed. Our timer is broken now, so this is kind of a silly example. But what if it were awaiting something like a long running calculation or a call to an external API? Then it would make a lot of sense to spawn a new process so your program could continue taking new requests.

Elixir processes are lightweight and fast. Each one keeps to itself. If one fails, it dies alone (*sniff*). They are not completely isolated, though. They can communicate via messages through a mail box.

Another other cool thing about processes is that they can store state. Although they don’t communicate directly, they can share this state. A process’ mail box can hold data to pass off to another process. This state can be read and manipulated by other processes.

Elixir provides a basic abstraction for working with processes. There are also more complex abstractions that offer some basic implementation details in a convenient package.

For example, if you are solely interested in managing/sharing state between processes check out Agent . If you are making a lot of asynchronous requests, try a Task . Task has a pattern like async/await in JavaScript. You can use it to make async code read in a linear fashion. If you want to schedule work to be done in the background on a periodic interval, try a GenServer . The latter is the one you are most likely to encounter in a production code base.

Another way to schedule background work is using an external package such as Oban. Although Oban doesn’t provide as much granular control over scheduling it provides some cool benefits such as a UI for monitoring and controlling background jobs. Personally, I’d start with a GenServer first then add another package only if needed. GenServer is built into the language so you don’t have to add any extra packages.

Spawning processes is efficient. On a machine with multiple processors they will run in parallel. If you were wondering what the difference is between concurrency and parallelism, there’s an easy way to think about this.

Work done concurrently is sort of like when you are making a sandwich and someone calls you. You will end up doing a little bit of sandwich-making and a little bit of talking until both are done. The sandwich will be made more slowly because you’re doing two things at once.

If you were able to spawn yourself and send your clone off to make the sandwich while you catch up with your bestie on the phone, that is more like running processes in parallel. Any basic laptop can give you the benefits of parallelism as they pretty much all have multiple core processors now. (Most have at least 4 and the 2020 MacBook Pro has 8.)

You might be wondering now how to manage timing when there are multiple processes starting and finishing jobs simultaneously. It is typical to have a main process that remains serialized while the child processes run through commands on their own time.

You might also be wondering what sort of data structure contains all of these processes? If you guessed a dictionary, you would be correct. This is good data structure for lookups as they can be done in constant time. This also means that processes all need to have a unique identifier, or key. They are born with a unique process ID, or PID. You can rename them to make managing them more intuitive, as long as they retain a unique name.

That’s all for now. Hopefully this has been helpful.