Lviv Elixir Meetup, 2016
Process is an instance of a computer program that is being executed (wiki)
A virtual machine to run Erlang and Elixir
Processes are lightweight and run across all CPUs.
It’s not uncommon to have thousands of concurrent processes in an Elixir application.
It's based on Actor model
iex> spawn(fn ->
iex> IO.puts "Hello, Elixir meetup!"
iex> end)
Hello, Elixir meetup!
#PID<0.77.0>
defmodule Example do
def add(a, b) do
IO.puts(a + b)
end
end
iex> spawn(Example, :add, [2, 3])
5
#PID<0.80.0>
iex> pid = spawn(fn -> IO.puts "Hi!" end)
Hi!
#PID<0.83.0>
iex> Process.alive? pid
false
defmodule Example do
def printer do
IO.puts "It's alive!"
Process.sleep 500
printer
end
end
iex> pid = spawn(Example, :printer, [])
It's alive!
#PID<0.69.0>
It's alive!
It's alive!
It's alive!
It's alive!
But not really.
Tail call optimization is there.
defmodule Example do
def listen do
receive do
{:ok, "hello"} -> IO.puts "World"
end
listen
end
end
iex> pid = spawn(Example, :listen, [])
#PID<0.108.0>
iex> send pid, {:ok, "hello"}
World
{:ok, "hello"}
iex> send pid, :ok
:ok
iex> self
#PID<0.56.0>
iex> spawn fn -> 5/0 end
#PID<0.62.0>
23:01:08.324 [error] Process #PID<0.62.0> raised an exception
** (ArithmeticError) bad argument in arithmetic expression
:erlang./(5, 0)
iex> self
#PID<0.56.0>
iex> self
#PID<0.56.0>
iex> spawn_link fn -> 5/0 end
** (EXIT from #PID<0.56.0>) an exception was raised:
** (ArithmeticError) bad argument in arithmetic expression
:erlang./(5, 0)
Interactive Elixir (1.3.0) - press Ctrl+C to exit (type h() ENTER for help)
23:02:34.029 [error] Process #PID<0.67.0> raised an exception
** (ArithmeticError) bad argument in arithmetic expression
:erlang./(5, 0)
iex> self
#PID<0.68.0>
iex> self
#PID<0.68.0>
iex> Process.flag(:trap_exit, true)
false
iex> spawn_link fn -> 5/0 end
#PID<0.85.0>
23:38:21.950 [error] Process #PID<0.85.0> raised an exception
** (ArithmeticError) bad argument in arithmetic expression
:erlang./(5, 0)
iex> self
#PID<0.68.0>
iex> flush
{:EXIT, #PID<0.85.0>, {:badarith, [{:erlang, :/, [5, 0], []}]}}
:ok
Supervisors are specialized processes with one purpose: monitoring other processes.
These supervisors enable us to create fault-tolerant applications by automatically restarting child processes when they fail.
import Supervisor.Spec
children = [
worker(SimpleQueue, [], [name: SimpleQueue])
]
{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)
There are currently four different restart strategies available to supervisors.
There are currently four different restart strategies available to supervisors:
:one_for_one - Only restart the failed child process.
There are currently four different restart strategies available to supervisors:
:one_for_one - Only restart the failed child process.
:one_for_all - Restart all child processes in the event of a failure.
There are currently four different restart strategies available to supervisors:
:one_for_one - Only restart the failed child process.
:one_for_all - Restart all child processes in the event of a failure.
:rest_for_one - Restart the failed process and any process started after it.
There are currently four different restart strategies available to supervisors:
:one_for_one - Only restart the failed child process.
:one_for_all - Restart all child processes in the event of a failure.
:rest_for_one - Restart the failed process and any process started after it.
:simple_one_for_one - Best for dynamically attached children. Supervisor is required to contain only one child type.
children = [
supervisor(App.Endpoint, []),
worker(App.Repo, []),
supervisor(App.EventStore.Supervisor, []),
]
opts = [strategy: :one_for_one, name: App.Supervisor]
Supervisor.start_link(children, opts)
A behavior is basically a message handling framework
OTP behaviors essentially supply:
Let's go step by step
defmodule SimpleQueue do
use GenServer
@doc """
Start our queue and link it.
"""
def start_link(state \\ []) do
GenServer.start_link(__MODULE__, state, name: __MODULE__)
end
@doc """
GenServer.init/1 callback
"""
def init(state), do: {:ok, state}
end
defmodule SimpleQueue do
use GenServer
### GenServer API
@doc """
GenServer.init/1 callback
"""
def init(state), do: {:ok, state}
@doc """
GenServer.handle_call/3 callback
"""
def handle_call(:queue, _from, state), do: {:reply, state, state}
### Client API / Helper methods
def start_link(state \\ []) do
GenServer.start_link(__MODULE__, state, name: __MODULE__)
end
def queue, do: GenServer.call(__MODULE__, :queue)
end
handle_call(request :: term, from, state :: term) ::
{:reply, reply, new_state} |
{:reply, reply, new_state, timeout | :hibernate} |
{:noreply, new_state} |
{:noreply, new_state, timeout | :hibernate} |
{:stop, reason, reply, new_state} |
{:stop, reason, new_state}
defmodule SimpleQueue do
use GenServer
### GenServer API
@doc """
GenServer.init/1 callback
"""
def init(state), do: {:ok, state}
@doc """
GenServer.handle_cast/2 callback
"""
def handle_cast({:enqueue, value}, state) do
{:noreply, state ++ [value]}
end
### Client API / Helper methods
def start_link(state \\ []) do
GenServer.start_link(__MODULE__, state, name: __MODULE__)
end
def enqueue(value), do: GenServer.cast(__MODULE__, {:enqueue, value})
end
handle_cast(request :: term, state :: term) ::
{:noreply, new_state} |
{:noreply, new_state, timeout | :hibernate} |
{:stop, reason :: term, new_state} when new_state: term
defmodule MyApp.Worker do
use GenServer
def start_link() do
GenServer.start_link(__MODULE__, [])
end
def init([]) do
schedule_work()
{:ok, []}
end
def handle_info(:work, state) do
state = do_work(state)
schedule_work()
{:noreply, state}
end
defp do_work(state) do
# Do your work here and return state
end
defp schedule_work do
Process.send_after(self(), :work, 60_000)
end
end
defmodule Example do
def double(x) do
:timer.sleep(2000)
x * 2
end
end
iex> task = Task.async(Example, :double, [2000])
%Task{pid: #PID<0.111.0>, ref: #Reference<0.0.8.200>}
iex> Task.await(task)
4000
O(log n) or O(1) access times!
iex> table = :ets.new(:meetups, [:set, :protected])
8212
iex> :ets.new(:meetups, [:set, :protected, :named_table])
:user_lookup
iex> :ets.insert(:meetups, {"Kyiv Elixir", "awesome", ["Elixir"]})
true
iex> :ets.lookup(:meetups, "Kyiv Elixir")
{"Kyiv Elixir", "awesome", ["Elixir"]}
iex> :ets.match(:meetups, {:"$1", "awesome", :"_"})
[["Kyiv Elixir"]]
iex> :ets.delete(:meetups, "Kyiv Elixir")
true
iex> :ets.delete(:meetups)
true
Alex Rozumii
@brain-geek