Language concurrency builtins
spawn
send
receive
monitor
link
Spawn a process
pid = spawn fn ->
IO.puts "Hello from #{inspect(self())}"
end
:timer.sleep(10)
Process.alive?(pid) |> IO.inspect # false
Communicate with a process
pid = spawn fn ->
receive do
{:ping, sender} -> send(sender, :pong)
end
end
:timer.sleep(10)
Process.alive?(pid) # true
send(pid, {:ping, self()})
flush() # :pong
Process.alive?(pid) # false
Building a server (kind of)
defmodule PingServer do
def start do
spawn(__MODULE__, :loop, [])
end
def loop do
receive do
{:ping, sender} ->
send(sender, :pong)
loop()
end
end
end
We need state!
- Elixir is immutable
- For state, use
Agent
s Agent
is a wrapper around GenServer
- We said no
GenServer
...
Recursive function arguments
def loop(some_int) do
receive do
{:add, value} ->
loop(some_int + value)
end
end
Our Stack
server
defmodule StackServer do
def loop(state) do
receive do
{:push, value} ->
new_state = [value | state]
loop(new_state)
{:pop, sender} ->
[value | new_state] = state
send(sender, value)
loop(new_state)
end
end
end
Abstract push
defmodule StackServer do
def push(pid, value) do
send(pid, {:push, value})
end
end
Make pop
synchronous
defmodule StackServer do
def pop(pid) do
ref = make_ref()
send(pid, {:pop, self(), ref})
receive do
{^ref, value} -> value
end
end
def loop(state) do
receive do
{:pop, sender, ref} ->
[value | new_state] = state
send(sender, {ref, value})
loop(new_state)
end
end
end
Make it generic
- A server has a state
- A server uses a main loop
- A server can handle sync/async requests
Refactor our loop
defmodule ClumsyGenServer do
def loop(module, state) do
receive do
{:async, message} ->
{:noreply, new_state} = module.handle_cast(message, state)
loop(module, new_state)
{:sync, message, sender, ref} ->
{:reply, reply, new_state} =
module.handle_call(message, {sender, ref}, state)
send(sender, {ref, reply})
loop(module, new_state)
end
end
end
Extract logic of push
and pop
defmodule ClumsyGenServer do
def cast(pid, message) do
send(pid, {:async, message})
end
def call(pid, message) do
ref = make_ref()
send(pid, {:sync, message, self(), ref})
receive do
{^ref, reply} -> reply
end
end
end
Logic to start ClumsyGenServer
defmodule ClumsyGenServer do
def start(module, state) do
spawn(__MODULE__, :init, [module, state])
end
def init(module, state) do
{:ok, state} = module.init(state)
loop(module, state)
end
...
end
Rewrite push
and pop
defmodule StackServer do
def init(state) do
{:ok, state}
end
def pop(pid) do
ClumsyGenServer.call(pid, :pop)
end
def push(pid, value) do
ClumsyGenServer.cast(pid, {:push, value})
end
def handle_cast({:push, value}, state) do
{:noreply, [value | state]}
end
def handle_call(:pop, _from, [head | rest]) do
{:reply, head, rest}
end
end