Elixir Note - Concurrency
For example, we have a simple module to fetch the price of a given ticker symbol (or stock symbol) from API and parse the results.
defmodule Worker do
def price_of(ticker) do
call_api(location) |> parse_data
end
defp call_api(ticker) do
# function to call api
end
defp parse_data do
end
end
Now we can start making our code concurrent, with two approaches:
1. Use Elixir primitive process:
defmodule Worker do
def loop do
receive do
{sender_pid, ticker} ->
send(sender_pid, {:ok, price_of(ticker)})
_ ->
IO.puts "don't know how to process this message"
end
end
def price_of(ticker) do
end
## Helper Functions
end
We use built-in spawn
function to create a process, send/2
to send the process we created. Finally, to get back responses from the shell session - use flush/0
function:
> pid = spawn(Worker, :loop, [])
> send(pid, {self, "AAPL"})
# {PID<0.343.0>, "AAPL"}
> flash
# {:ok, "AAPL: 140,2"}
2. Use OTP GenServer
defmodule Worker do
use GenServer
## Client API
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, :ok, opts)
end
def get_price(pid, ticker) do
GenServer.call(pid, {:ticker, ticker})
end
## Server Callbacks
def init(:ok) do
{:ok, %{}}
end
def handle_call({:location, ticker}, _from, stats) do
case price_of(ticker) do
{:ok, result} -> {:reply, result, stats}
_ -> {:reply, :error, stats}
end
end
## Helper Functions
end
Using GenServer behavior is more simple and straightforward. We don't need to walk through some steps: spawn process -> send and receive message -> flush results.
> {:ok, pid} = Worker.start_link
> Worker.get_price(pid, "AAPL")
# "140,2"
Note about GenServer :
GenServer module calls | Callback module |
---|---|
GenServer.start_link |
Worker.init/1 |
GenServer.call/3 |
Worker.handle_call/3 |
GenServer.cast/2 |
Worker.handle_cast/2 |