|

The power and ease of working on multiple processes with elixir

Writer: William Freire, Backend Engineer

One of Elixir’s key features is the idea of packaging code into small chunks that can be run independently and concurrently.

If you’ve come from a conventional programming language, this may worry you. Concurrent programming is known to be difficult, and there’s a performance penalty to pay when you create lots of processes.

Thanks to the architecture of the Erlang VM, where Elixir runs, Elixir doesn’t have these issues.

When we talk about processes in Elixir, we aren’t talking about native operating-system processes. These are too slow. Instead, Elixir uses the actor model of concurrency. An actor is an independent process that shares nothing with any other process. You can spawn new processes, send them messages, and receive messages back.

Normally, you may have had to use threads or operating systems processes to achieve concurrency. You probably had some boring problems by working this way. But using Elixir, these problems just disappear.

Here, let’s write our first process:

processes/spawner.ex

defmodule Spawner do
  def greet do
    IO.puts(“Hello”)
  end
end

And done. A normal code.

Now let’s bring in IEx and compile our code:

iex> c(“spawner.ex”)
[Spawner]

Let’s call it as a regular function: 

iex> Spawner.greet
Hello
:ok

Now let’s run it as a process:

iex> spawn(Spawner, :greet, [])
Hello
#PID<0.116.0>

The spawn returns a process identifier, also called PID. A PID is used to identify a process created.

When we call the method spawn, it creates a new process to run the code we specify.

In this example, we can see that our function ran and printed for us “Hello” prior to IEx showing the PID returned by spawn. But you can’t rely on this. Instead, you’ll use messages to synchronize your processes.

Let’s rewrite our example to use messages between processes. We’ll send a message containing a string to greet function and the greet function will respond with a greeting, containing the message.

We send a message in Elixir using the send function. It receives a PID and the message to send. You can send anything you want. Normally Elixir developers prefer to use atoms and tuples.

We wait for a message using receive. This method goes through the current process mailbox searching for a message that matches any of the given patterns and the first pattern that matches the function is run.

Our new version of greet function:

processes/spawner.ex

defmodule Spawner do
  def greet do
    receive do
      {sender, message} ->
        send(sender, {:ok, “Hello, #{message}”})

      _ ->
        raise “error”
    end
  end
end

Let’s create a sender:

processes/sender.ex

defmodule Sender do
  def call_spawner do
    pid = spawn(Spawner, :greet, [])

    send(pid, {self(), “World!”})

    receive do
      {:ok, message} ->
        IO.puts(message)
    end
  end
end

Now let’s compile our files:

iex> c([“spawner.ex”, “sender.ex”])
[Sender, Spawner]
iex> Sender.call_spawner
Hello, World!:ok

Nice, the processes are talking to each other. But what if we try to send a second message? 

processes/sender.ex

defmodule Sender do
  def call_spawner do
    pid = spawn(Spawner, :greet, [])

    send(pid, {self(), “World!”})

    receive do
      {:ok, message} ->
        IO.puts(message)
    end

    send(pid, {self(), “Brazil!”})

    receive do
      {:ok, message} ->
        IO.puts(message)
    end
  end
end

Let’s call our sender again. First, we must compile our module once more:

iex> c(“sender.ex”)
[Sender]
iex> Sender.call_spawner
Hello, World!… OK, Just It …

Our first message arrived, but the second didn’t. And worse, our IEx is hanging, and is necessary to kill it.

This happens because our greet function handles a message once and after that, it exits. Our second message never is processed and the second receiver just waits for a response that will never come.

So to not leave our sender hanging, we are going to use after. With after, we can set a time to receive a response and after this time, we will print an error. 

So, let’s fix it:

processes/sender.ex

defmodule Sender do
  def call_spawner do
    pid = spawn(Spawner, :greet, [])

    send(pid, {self(), “World!”})

    receive do
      {:ok, message} ->
        IO.puts(message)
    end

    send(pid, {self(), “Brazil!”})

    receive do
      {:ok, message} ->
        IO.puts(message)
    after
>>   500 ->
>>     IO.puts(“The Generator is no longer here”)
    end
  end
end
iex> c(“sender.ex”)
[Sender]
iex> Sender.call_spawner
Hello, World!The Generator is no longer here:okiex>

But we want to make our spawner handle multiple messages. Normally, to do this, we’ll make it loop, doing a receive on each iteration. But Elixir doesn’t have loops, it has recursion.

At our spawner, we’ll put just a row. This row is going to resolve the problem, so that our greet function handles just one message at a time.

processes/spawner.ex

defmodule Spawner do
  def greet do
    receive do
      {sender, message} ->
        send(sender, {:ok, “Hello, #{message}”})>>       greet()
      _ ->
        raise “error”
    end
  end
end
iex> c(“spawner.ex”)
[Spawner]
iex> Sender.call_spawner
Hello, World!Hello, Brazil!:okiex>

Maybe calling the greet function again at the end of the function has worried you. It always ends up calling itself. In the majority of languages, that would add a new frame to the stack. And after a large number of messages, you might run out of memory.

This doesn’t happen with Elixir. If the last thing a function does is call itself, there’s no need to make the call. The function just jumps back to the start of the function. If the recursive call has arguments, then these replace the original parameters.

Conclusion

So we talked about the problems of working with processes and how hard it is to orchestrate processes. 

We also touch on the point of how easy it is to work with processes and how Elixir handles these problems.

We learned how to create a simple process without GenServer. And maybe “how to use GenServer and OTP concurrency” can be our next piece.

I hope that this article helps you to venture in the Elixir land.

Similar Posts

Leave a Reply