Select in Elixir

Our example demonstrates the use of receive, which allows us to wait on multiple message operations. Combining processes and message passing with receive is a powerful feature of Elixir.

defmodule Select do
  def run do
    # For our example we'll receive from two processes.
    pid1 = spawn(fn -> send_after_delay("one", 1000) end)
    pid2 = spawn(fn -> send_after_delay("two", 2000) end)

    # Each process will send a message after some amount
    # of time, to simulate e.g. blocking operations
    # executing in concurrent processes.

    # We'll use `receive` to await both of these messages
    # simultaneously, printing each one as it arrives.
    for _ <- 1..2 do
      receive do
        {:msg, pid, msg} when pid == pid1 ->
          IO.puts("received #{msg}")
        {:msg, pid, msg} when pid == pid2 ->
          IO.puts("received #{msg}")
      end
    end
  end

  defp send_after_delay(msg, delay) do
    :timer.sleep(delay)
    send(self(), {:msg, self(), msg})
  end
end

Select.run()

We receive the values “one” and then “two” as expected.

$ elixir select.exs
received one
received two

Note that the total execution time is only ~2 seconds since both the 1 and 2 second delays execute concurrently.

real    0m2.245s

In this Elixir version:

  1. We use spawn to create new processes, which are similar to goroutines in their lightweight nature.
  2. Instead of channels, we use Elixir’s built-in message passing system.
  3. The receive block is used to wait for and handle messages, similar to the select statement in the original example.
  4. We use pattern matching in the receive block to differentiate between messages from different processes.
  5. The :timer.sleep/1 function is used to introduce delays, similar to time.Sleep in the original.

This example showcases Elixir’s concurrency model, which is based on the Actor model, providing a different but equally powerful approach to concurrent programming compared to Go’s goroutines and channels.