On Github miguelcoba / elixir-concurrency
Elixir is a dynamic, functional language designed for building scalable and maintainable applications
Lightweight threads of execution
Isolated
Exchange information via messages
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/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
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
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 message to self
iex> send self(), {:hello, "world"} {:hello, "world"} iex> receive do ...> {:hello, msg} -> msg ...> {:world, msg} -> "won't match" ...> end
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
Specify a timeout when receiving messages
receive do {:hello, msg} -> msg after 1000 -> "nothing happened" end
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>"
spawn version
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
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
$ 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
$ 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>
$ 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>
iex(one@arwen)1> c "server.ex" [Server] iex(one@arwen)2>
iex(two@arwen)3> c "client.ex" [Client] iex(two@arwen)4>
iex(one@arwen)2> Server.start :yes Just here, waiting... Just here, waiting... Just here, waiting...
iex(two@arwen)4> Client.start Got 0 Got 1 Got 2 Got 3
# 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>
# 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>
iex(server@192.168.0.1)1> c "server.ex" [Server] iex(server@192.168.0.1)2>
iex(client@192.168.0.2)3> c "client.ex" [Client] iex(client@192.168.0.2)4>
iex(server@192.168.0.1)2> Server.start :yes Just here, waiting... Just here, waiting... Just here, waiting...
iex(client@192.168.0.2)4> Client.start Got 0 Got 1 Got 2 Got 3
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
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
GemServer version
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
defmodule GenServer.Client do def start do loop() end def loop do counter = GenServer.Server.next() IO.puts "Got #{counter}" loop() end end
$ 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
$ 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]
$ 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]
iex(one@arwen)2> GenServer.Server.start {:ok, #PID<0.96.0>} iex(one@arwen)3>
iex(two@arwen)5> GenServer.Client.start Got 0 Got 1 Got 2 Got 3
# 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>
# 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>
iex(server@192.168.0.1)2> c "server.ex" [GenServer.Server] iex(server@192.168.0.1)3>
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>
iex(server@192.168.0.1)7> GenServer.Server.start {:ok, #PID<0.96.0>} iex(server@192.168.0.1)8>
iex(client@192.168.0.2)7> GenServer.Client.start Got 0 Got 1 Got 2 Got 3
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
http://miguelcoba.github.io/elixir-concurrency
Miguel Cobá
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.