Spawning processes in Elixir, a gentle introduction to concurrency
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
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
·
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…
(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?
Copy link
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).
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
Discussion Favorited!
Favoriting means this is a discussion worth sharing. It gets shared to your followers' Disqus feeds, and gives the creator kudos!
Tweet this discussion
- Share this discussion on Facebook
- Share this discussion via email
- Copy link to discussion
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
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
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
Nerves powered Vision – Deploy YOLOv8 on RPi5 with…
Sep 5, 202514 sec read
Building a YOLOX Plate Detector – Setup, Fine-Tuning, Metrics,…
Aug 29, 20253 min read
Fine-Tuning YOLO to Watch Soccer Matches
Jul 17, 20255 min read
Twitter Widget Iframe