Elixir – Concurrency 101 – Processes



Elixir – Concurrency 101 – Processes

0 1


elixir-concurrency

Elixir Concurrency 101 http://miguelcoba.github.io/elixir-concurrency

On Github miguelcoba / elixir-concurrency

Elixir

Concurrency 101

Miguel Cobá / @MiguelCobaMtz

Elixir

Elixir is a dynamic, functional language designed for building scalable and maintainable applications

Processes

Lightweight threads of execution

Isolated

Exchange information via messages

Simple example

parent = self()

# Spawns an Elixir process (not an operating system one!)
spawn(fn ->
  send parent, {:msg, "hello world"}
end)

# Block until the message is received
receive do
  {:msg, contents} -> IO.puts contents
end

spawn

spawn/1 takes a function which it will execute in another process.

Returns a PID (process identifier)

We can retrieve the PID of the current process by calling self/0

Process.alive?/1 can check if a process exists and is alive

spawn example

iex> pid = spawn fn -> 1 + 2 end
#PID<0.43.0>
iex> Process.alive?(pid) # process is dead by now
false
iex> self()
#PID<0.41.0>
iex> Process.alive?(self())
true

send and receive

We can send messages to a process with send/2 and receive them with receive/1

The process can be anyone, including the process itself

send and receive example

Send message to self

iex> send self(), {:hello, "world"}
{:hello, "world"}
iex> receive do
...>   {:hello, msg} -> msg
...>   {:world, msg} -> "won't match"
...> end

send and receive 2

Each process has a mailbox to receive messages

Messages in mailbox are ordered by arrival time

If there are no messages in the mailbox, or if none of them matches any of the patterns, the current process will wait until a matching message arrives

receive/1 can specify a timeout if needed

send and receive example 2

Specify a timeout when receiving messages

receive do
  {:hello, msg}  -> msg
after
  1000 -> "nothing happened"
end

send and receive example 3

Send and receive messages between processes

iex> parent = self()
#PID<0.41.0>
iex> spawn fn -> send(parent, {:hello, self()}) end
#PID<0.48.0>
iex> receive do
...>   {:hello, pid} -> "Got hello from #{inspect pid}"
...> end
"Got hello from #PID<0.48.0>"

Counter example

spawn version

server.ex

defmodule Server do
  def start do
    pid = spawn(Server, :loop, [0])
    :global.register_name(:counter, pid)
  end

  def loop(counter) do
    receive do
      {:next, sender} -> send sender, {:ok, counter}
      loop(counter + 1)
    after
      2000 -> IO.puts "Just here, waiting..."
      loop(counter)
    end
  end
end

client.ex

defmodule Client do
  def start do
    counter_pid = :global.whereis_name(:counter)
    loop(counter_pid)
  end

  def loop(counter_pid) do
    send counter_pid, {:next, self()}
    receive do
      {:ok, counter} -> IO.puts "Got #{counter}"
    after
      1000 -> "No answer :("
    end
    loop(counter_pid)
  end
end

Connecting client to server

Single node

Terminal

$ iex
Erlang/OTP 18 [erts-7.2.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.2.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> c "server.ex"
[Server]
iex(2)> c "client.ex"
[Client]
iex(3)> Server.start
:yes
Just here, waiting...
Just here, waiting...
iex(4)> Client.start
Got 0
Got 1
Got 2

Connecting client to server

Multi node

Start nodes and connect them

Terminal 1

$ iex --sname one
Erlang/OTP 18 [erts-7.2.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.2.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(one@arwen)1>

Terminal 2

$ iex --sname two
Erlang/OTP 18 [erts-7.2.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.2.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(two@arwen)1> Node.connect :"one@arwen"
true
iex(two@arwen)2> Node.list
[:one@arwen]
iex(two@arwen)3>

Load code

Terminal 1

iex(one@arwen)1> c "server.ex"
[Server]
iex(one@arwen)2>

Terminal 2

iex(two@arwen)3> c "client.ex"
[Client]
iex(two@arwen)4>

Connect client to server

Terminal 1

iex(one@arwen)2> Server.start
:yes
Just here, waiting...
Just here, waiting...
Just here, waiting...

Terminal 2

iex(two@arwen)4> Client.start
Got 0
Got 1
Got 2
Got 3

Connecting client to server

Multi node, multi machine

Start nodes and connect them

Machine 1

# Determine your server machine ip with ipconfig (Win) or ifconfig (Mac, GNU/Linux)
$ iex --name server@192.168.0.1 --cookie elixir
Erlang/OTP 18 [erts-7.2.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.2.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(server@192.168.0.1)1>

Machine 2 (or 3 or 4,...)

# Determine your client machine ip with ipconfig (Win) or ifconfig (Mac, GNU/Linux)
$ iex --name client@192.168.0.2 --cookie elixir
Erlang/OTP 18 [erts-7.2.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.2.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(client@192.168.0.2)1> Node.ping :"server@192.168.0.1"
:pong
iex(client@192.168.0.2)1> Node.connect :"server@192.168.0.1"
true
iex(client@192.168.0.2)2> Node.list
[:server@192.168.0.1]
iex(client@192.168.0.2)3>

Load code

Machine 1

iex(server@192.168.0.1)1> c "server.ex"
[Server]
iex(server@192.168.0.1)2>

Machine 2

iex(client@192.168.0.2)3> c "client.ex"
[Client]
iex(client@192.168.0.2)4>

Connect client to server

Machine 1

iex(server@192.168.0.1)2> Server.start
:yes
Just here, waiting...
Just here, waiting...
Just here, waiting...

Machine 2

iex(client@192.168.0.2)4> Client.start
Got 0
Got 1
Got 2
Got 3

GenServer

It is a behaviour module for implementing the server part of a client-server relation

It is a process like any other Elixir process

It can be used to keep state, execute code asynchronously

It has a standard set of interface functions

It includes functionality for tracing and error reporting

GenServer example

defmodule Stack do
  use GenServer

  # Callbacks
  def handle_call(:pop, _from, [h|t]) do
    {:reply, h, t}
  end

  def handle_cast({:push, item}, state) do
    {:noreply, [item|state]}
  end
end

# Start the server
{:ok, pid} = GenServer.start_link(Stack, [:hello])

# This is the client
GenServer.call(pid, :pop)
#=> :hello

GenServer.cast(pid, {:push, :world})
#=> :ok

GenServer.call(pid, :pop)
#=> :world

Counter example

GemServer version

server.ex

defmodule GenServer.Server do
  use GenServer

  def start do
    GenServer.start_link(GenServer.Server, 0, name: {:global, :counter})
  end

  def next do
    GenServer.call({:global, :counter}, :next)
  end

  # Implementation
  def handle_call(:next, _from, counter) do
    {:reply, counter, counter + 1}
  end
end

client.ex

defmodule GenServer.Client do
  def start do
    loop()
  end

  def loop do
    counter = GenServer.Server.next()
    IO.puts "Got #{counter}"
    loop()
  end
end

Connecting client to server

Single node

Terminal

$ iex
Erlang/OTP 18 [erts-7.2.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.2.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> c "server.ex"
[GenServer.Server]
iex(2)> c "client.ex"
[GenServer.Client]
iex(3)> GenServer.Server.start
{:ok, #PID<0.98.0>}
iex(4)> GenServer.Client.start
Got 0
Got 1
Got 2
Got 3

Connecting client to server

Multi node

Start nodes and connect them

Terminal 1

$ iex --sname one
Erlang/OTP 18 [erts-7.2.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.2.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(one@arwen)1> c "server.ex"
[GenServer.Server]

Terminal 2

$ iex --sname two
Erlang/OTP 18 [erts-7.2.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.2.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(one@arwen)1> c "server.ex"
[GenServer.Server]
iex(one@arwen)2> c "client.ex"
[GenServer.Client]
iex(two@arwen)3> Node.connect :"one@arwen"
true
iex(two@arwen)4> Node.list
[:one@arwen]

Connect client to server

Terminal 1

iex(one@arwen)2> GenServer.Server.start
{:ok, #PID<0.96.0>}
iex(one@arwen)3>

Terminal 2

iex(two@arwen)5> GenServer.Client.start
Got 0
Got 1
Got 2
Got 3

Connecting client to server

Multi machine

Start nodes and connect them

Machine 1

# Determine your server machine ip with ipconfig (Win) or ifconfig (Mac, GNU/Linux)
$ iex --name server@192.168.0.1 --cookie elixir
Erlang/OTP 18 [erts-7.2.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.2.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(server@192.168.0.1)1>

Machine 2

# Determine your client machine ip with ipconfig (Win) or ifconfig (Mac, GNU/Linux)
$ iex --name client@192.168.0.2 --cookie elixir
Erlang/OTP 18 [erts-7.2.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.2.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(client@192.168.0.2)1> Node.connect :"server@192.168.0.1"
true
iex(client@192.168.0.2)2> Node.list
[:server@192.168.0.1]
iex(client@192.168.0.2)3>

Load code

Machine 1

iex(server@192.168.0.1)2> c "server.ex"
[GenServer.Server]
iex(server@192.168.0.1)3>

Machine 2

iex(server@192.168.0.1)4> c "server.ex"
[GenServer.Server]
iex(client@192.168.0.2)5> c "client.ex"
[GenServer.Client]
iex(client@192.168.0.2)6>

Connect client to server

Machine 1

iex(server@192.168.0.1)7> GenServer.Server.start
{:ok, #PID<0.96.0>}
iex(server@192.168.0.1)8>

Machine 2

iex(client@192.168.0.2)7> GenServer.Client.start
Got 0
Got 1
Got 2
Got 3

GenServer summary

Only required to implement the callbacks and functionality we are interested in

call messages expect a reply from the server (and are therefore synchronous)

cast messages do not

Slides

http://miguelcoba.github.io/elixir-concurrency

Source code

https://github.com/miguelcoba/elixir-concurrency-code

Thank you!

Miguel Cobá

This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
Elixir Concurrency 101 Miguel Cobá / @MiguelCobaMtz