Spawning processes in Elixir, a gentle introduction to concurrency

Alvise Susmel About

March 6, 2019

X Follow Button

Along with pattern matching, one of the coolest things in Erlang and Elixir is their concurrency implementation based on Actor model. In this article I introduce concurrency and show how we can start making our code concurrent in Elixir, using processes.

Concurrency

We can think of concurrency as dealing with multiple things happening, and progressing, at the same time.

Concurrency is not to be confused with parallelism. They are two different concepts that sometime are used like synonyms. We can have multiple things running at the same time on just one CPU (or core); they progress together, but they are not executed in parallel.

Quoting Rob Pike

Concurrency is the composition of independently executing things, typically functions

Parallelism is the simultaneous execution of multiple things, possibly related, possibly not

Rob Pike – Concurrency Is Not Parallelism

Once we are able to split our problem into sub-tasks, and make it concurrent, we are then also able to take advantage of multiple cores and run the different sub-tasks in parallel.

I’ll write more on this in further articles.

Erlang processes

To make our code concurrent in Elixir we use Erlang processes.

If you are coming from programming languages like Ruby, Python or Java, you may have used OS threads, or OS processes, to make your code concurrent.

I developed for many many years with Rails and Sinatra frameworks and I really love Ruby, they both make a joy developing services. In the last five years I’ve also used Python quite extensively, especially to take advantage of the fantastic machine learning libraries that Python community has.

BUT if you developed with one of these two languages you maybe know they have something called GIL (global interpreter lock). In short, the GIL ensures that only one thread at a time can access to the shared memory. Don’t get me wrong, the GIL makes easier to write thread-safe code, but it also makes really difficult to write code that can scale out running in parallel on multiple cores.

These languages were not built with concurrency as the main goal. And concurrency is not just matter of scaling out, it’s for modelling the real world, which is mainly concurrent. The Erlang and Elixir concurrency model brings isolation, fault-tolerance and a great way to coordinate and distribute processes.

Erlang processes are not OS threads. They are not even OS processes. Erlang processes are lighter than threads, they have a really small memory footprint and the context switching is much faster.

Twitter Embed

Visit this post on X

Joe Armstrong

@joeerl

·

Follow

View on X

Erlang processes are emulated in the Erlang VM, like Green threads - we like them since this simplifies many problems - other languages prefer efficiency - we want things like dynamic code upgrade which is difficult with native threads. https://x.com/pankajdoharey/status/1010461472650956800…

Visit this post on X

(identity '[:pankaj :λ])

@pankajdoharey

@joeerl Are Erlang processes Green Threads ? And if so why does every VM community tries to abandon them (i.e Java) and Erlang embraces them?

11:33 AM · Jun 23, 2018

X Ads info and privacy

86 Reply

Copy link

Read 2 replies

The reason because Erlang and Elixir are highly concurrent, is because processes are so cheap that is possible to easily spawn thousands of them, without using all the memory.

Since the web Phoenix Framework is built with Elixir, it inherits its highly concurrent nature. The phoenix core-team ran a test few years ago showing how they got 2 millions active WebSocket connections on a 40 cores, 128GB ram machine.

So, since an Erlang process is lighter than a thread and a OS process, why is it called a process?

the term “process” is usually used when the threads of execution share no data with each other and the term “thread” when they share data in some way. Threads of execution in Erlang share no data, that is why they are called processes).

Erlang.org – Concurrent Programming

Let’s see something in practice!

Make HTTP API requests concurrent

Let’s consider this example. We have a simple get_price function that makes an HTTP request to get a cryptocurrency price from the Coinbase API.

defmodule Coinbase do
  @coinbase_url "https://api.pro.coinbase.com"

def get_price(product_id) do
    url = "#{@coinbase_url}/products/#{product_id}/ticker"

%{"price" => price} =
      HTTPoison.get!(url).body
      |> Jason.decode!()

price
  end
end

Elixir

Copy

The function uses HTTPoison and Jason to get the price of the given product_id.

We also add a second function to the module.

def print_price(product_id) do
  start = System.monotonic_time(:millisecond)

price = get_price(product_id)

stop = System.monotonic_time(:millisecond)
  time = (stop - start) / 1000
  IO.puts("#{product_id}: #{price}\ttime: #{time}s")
end

Elixir

Copy

print_price(product_id) helps us to see how much time get_price(product_id) needs to request the price and return with a result. We simply surround the function with a start and stop timestamps and then calculate the difference to get the number of seconds elapsed.

start = System.monotonic_time(:millisecond)
...
stop = System.monotonic_time(:millisecond)

Elixir

Copy

Since both our functions accept a product_id, we can use them to get prices of multiple products. For example, for Bitcoin (BTC-USD), Ethereum (ETH-USD), Litecoin (LTC-USD) and Bitcoin Cash (BCH-USD).

Coinbase.print_price "BTC-USD"
Coinbase.print_price "ETH-USD"
Coinbase.print_price "LTC-USD"
Coinbase.print_price "BCH-USD"

Elixir

Copy

Or in a nicer functional way

["BTC-USD", "ETH-USD", "LTC-USD", "BCH-USD"]
|> Enum.each( &Coinbase.print_price/1 )

Elixir

Copy

Running these requests we get the updated prices along with the time needed to complete each request.

BTC-USD: 3708.29000000  time: 0.125s
ETH-USD: 125.44000000   time: 0.171s
LTC-USD: 45.71000000    time: 0.481s
BCH-USD: 122.91000000   time: 0.187s

Elixir

Copy

Each single product is requested sequentially. We first request for BTC-USD and wait for the response, then we request for ETH-USD and so on…

Sequential HTTP requests in Erlang and Elixir

The problem with making requests one after the other, is that in this case there isn’t much computation happening and our computer is idle most of the time just waiting for the response from the server.

So, how can we request the four prices together, without each request has to wait that the previous one has finished?

Spawning processes

With the spawn/1 function we can easily make the previous requests concurrent, so each request is made and carried out at the same time.

pid = spawn fn ->
  # our code
end

Elixir

Copy

The spawn(func) function creates an Erlang process, returns a PID (a unique Process ID) and runs the passed function inside this new process.

We use the PID to get informations about the process, interact and, most importantly to communicate with it (which is something we will see in next articles).

So, let’s make our requests concurrent, running each one of them in its own process.

pid_btc = spawn fn ->
  Coinbase.print_price("BTC-USD")
end

pid_eth = spawn fn ->
  Coinbase.print_price("ETH-USD")
End
...

Elixir

Copy

or as we did before we can use a much more compact way, enumerating the cryptocurrencies and passing them to the function we want to run in a different process

iex> ["BTC-USD", "ETH-USD", "LTC-USD", "BCH-USD"] \
|> Enum.map(fn product_id->
    spawn(fn -> Coinbase.print_price(product_id) end)
end)

[#PID<0.206.0>, #PID<0.207.0>, #PID<0.208.0>, #PID<0.209.0>]
BTC-USD: 3704.51000000  time: 0.234s
ETH-USD: 125.15000000   time: 0.234s
LTC-USD: 45.64000000    time: 0.324s
BCH-USD: 122.83000000   time: 0.325s

Elixir

Copy

This time each request is made at the same time. The Enum.map function enumerates the product list and spawns a new process for each product in the list, running the Coinbase.print_price(product_id) function in it. The result is a list of PIDs.

Here a diagram showing how the requests are made concurrently

Spawn multiple processes to concurrently request prices

Wrap Up

We saw how easy is to spawn processes and making our code concurrent. But this is just the beginning. We had to print the results because the spawn/1 function returns immediately a PID, and we weren’t able to get the result in a traditional way. To coordinate with processes and communicate with them we still need to see an important piece of the puzzle: message passing, which I will cover in the next article.

Share this:

Disqus Recommendations

We were unable to load Disqus Recommendations. If you are a moderator please see our troubleshooting guide.

  • 6 years ago
  • 8 comments

This article is about Elixir-Python interoperability using Elixir Port and how to …

  • 7 years ago
  • 7 comments

Part 1 – Elixir Stream to process large HTTP responses on the fly Part …

  • 7 years ago
  • 8 comments

Phoenix LiveView pushstate support bring the ability to change the URL without …

  • 2 years ago
  • 1 comment

As someone who loves experimenting with new technologies, I recently …

  • 7 years ago
  • 6 comments

In this article we see how to build a Gallery app with Phoenix LiveView and …

  • 7 years ago
  • 3 comments

DigitalOcean Spaces is a cloud storage alternative to AWS S3. Since Spaces is …

  • 5 years ago
  • 4 comments

In this video we take a look at the Poeticoins application design, how we organize …

  • 6 years ago
  • 3 comments

Bakeware is a new fantastic tool, which compiles an Elixir, a Scenic or a …

tempest.services.disqus.com

tempest.services.disqus.com is blocked

This page has been blocked by an extension

  • Try disabling your extensions.

ERR_BLOCKED_BY_CLIENT

Reload

This page has been blocked by an extension

Disqus Comments

We were unable to load Disqus. If you are a moderator please see our troubleshooting guide.

G

Join the discussion…

Comment

Log in with
or sign up with Disqus or pick a name

Disqus is a discussion network

  • Don't be a jerk or do anything illegal. Everything is easier that way.

Read full terms and conditions

This comment platform is hosted by Disqus, Inc. I authorize Disqus and its affiliates to:

  • Use, sell, and share my information to enable me to use its comment services and for marketing purposes, including cross-context behavioral advertising, as described in our Terms of Service and Privacy Policy, including supplementing that information with other data about me, such as my browsing and location data.
  • Contact me or enable others to contact me by email with offers for goods or services
  • Process any sensitive personal information that I submit in a comment. See our Privacy Policy for more information

Acknowledge I am 18 or older

  • 5

  • Discussion Favorited!

Favoriting means this is a discussion worth sharing. It gets shared to your followers' Disqus feeds, and gives the creator kudos!

Find More Discussions

Share

M

Why does the spawn process take longer to process each request?

see more

On average they shouldn't. The timings above are real and these measures bring some variance. For example, I've just run the code again now, getting

ETH-USD: 184 time: 0.135s BCH-USD: 292.7 time: 0.139s BTC-USD: 9259.98 time: 0.142s LTC-USD: 60.43 time: 0.145s

Something to take in consideration, though: In the first example, when we do one request at a time, we have the whole internet connection for just one download. In the last one we have 4 parallel downloads. Now, with this kind of requests it shouldn't make almost any difference, But with many and heavier parallel requests each one could be slower, since they share the same internet connection.

see more

Show more replies

A

Thanks, Alvise!

see more

it's good article, Alvise

anyway, could you please tell me what tool you use to draw the images above?

see more

Thanks :D I use lucidcharts.com, which I really like. Let me know if you find any interesting alternative.

see more

I'm using this one https://www.draw.io/

it isn't fancy as lucidcharts :D

see more

Load more comments

live.rezync.com

live.rezync.com is blocked

This page has been blocked by an extension

  • Try disabling your extensions.

ERR_BLOCKED_BY_CLIENT

Reload

This page has been blocked by an extension

pippio.com

pippio.com is blocked

This page has been blocked by an extension

  • Try disabling your extensions.

ERR_BLOCKED_BY_CLIENT

Reload

This page has been blocked by an extension

tempest.services.disqus.com

tempest.services.disqus.com is blocked

This page has been blocked by an extension

  • Try disabling your extensions.

ERR_BLOCKED_BY_CLIENT

Reload

This page has been blocked by an extension

Elixir

Nerves powered Vision – Deploy YOLOv8 on RPi5 with…

Alvise Susmel

Sep 5, 202514 sec read

Elixir

Building a YOLOX Plate Detector – Setup, Fine-Tuning, Metrics,…

Alvise Susmel

Aug 29, 20253 min read

Elixir

Fine-Tuning YOLO to Watch Soccer Matches

Alvise Susmel

Jul 17, 20255 min read

Twitter Widget Iframe