43
第3回 - プロセスだ 1 - GMO Pepabo, Inc. Joe Honzawa 2015/5/26 Elixir勉強会 Elixirだ

Elixirだ 第3回

  • Upload
    joenoh

  • View
    315

  • Download
    2

Embed Size (px)

Citation preview

Page 1: Elixirだ 第3回

第3回 - プロセスだ 1 -

GMO Pepabo, Inc. Joe Honzawa

2015/5/26 Elixir勉強会

Elixirだ

Page 2: Elixirだ 第3回

前回やったこと> メタプログラミング > ElixirのAST > マクロ > assert > コード生成

Page 3: Elixirだ 第3回

今回の内容> Erlangプロセス > メッセージング > 状態を持ったプロセス > GenServer > Agent > Task

Page 4: Elixirだ 第3回

Erlangプロセス

Page 5: Elixirだ 第3回

プロセスたちErlang VM

P1P3

P2

Page 6: Elixirだ 第3回

Erlang VM

P1P3

P2

プロセスたち

Erlangプロセス

OSプロセス

Page 7: Elixirだ 第3回

プロセス> 識別子はPID > メッセージキューを持つ > 他プロセスとメモリを共有しない > プリエンプティブ・スケジューリング > 優先度がある > メッセージ待ちのプロセスは低優先度

Page 8: Elixirだ 第3回

プロセスの生成

iex(1)> spawn(fn -> 1+1 end) #PID<0.XX.X>

iex(2)> spawn(M, :fun, [:args]) #PID<0.YY.Y>

Page 9: Elixirだ 第3回

メッセージング

Page 10: Elixirだ 第3回

メッセージング> sendで送信 > receiveで受信 > afterでタイムアウト時間(ms)を指定 > iexではflush/0が使える > メールボックス内を全部表示する > メールボックスは空になる

Page 11: Elixirだ 第3回

送ろうiex(1)> me = self() # 自分のPID

iex(2)> send(me, “hello”) iex(3)> send(me, {:user, 123})

iex(4)> flush

Page 12: Elixirだ 第3回

受けようiex(5)> send self, {:user, 123}

iex(6)> receive do ...(6)> {:user, id} -> ...(6)> IO.puts id ...(6)> _other -> ...(6)> IO.puts “?” ...(6)> end

Page 13: Elixirだ 第3回

タイムアウト

iex(7)> receive do ...(7)> {:user, id} -> ...(7)> IO.puts id ...(7)> after 1000 -> ...(7)> IO.puts "too slow" ...(7)> end

Page 14: Elixirだ 第3回

状態を持ったプロセス

Page 15: Elixirだ 第3回

さっきのやつ

iex(1)> spawn(fn -> 1+1 end) #PID<0.XX.X>

Page 16: Elixirだ 第3回

死んでる

iex(2)> Process.alive? v(1) false

Page 17: Elixirだ 第3回

死んでた> 計算が終わって停止した > ステートフル > すぐに死なれると困る > 受信・返信できて欲しい

> 繰り返しreceiveさせる

Page 18: Elixirだ 第3回

こうして

$ mix new uuid

Page 19: Elixirだ 第3回

こうしてdefmodule Uuid do def loop(current \\ 0) do receive do {:next, reply_to} -> send(reply_to, current) loop(current+1) _other -> loop(current) end end end

Page 20: Elixirだ 第3回

こうだ$ iex -S mix

iex(1)> pid = spawn Uuid, :loop, []

iex(2)> send pid, {:next, self} iex(3)> send pid, {:next, self} iex(4)> send pid, {:next, self}

iex(5)> flush

Page 21: Elixirだ 第3回

OTP

Page 22: Elixirだ 第3回

OTP> Erlangアプリケーションのための > ライブラリ > フレームワーク > デザインパターン

> gen_server, ets, mnesia など

Page 23: Elixirだ 第3回

GenServer> っていうElixirモジュール > 中身はErlangのgen_server > 引数の順番などをElixirっぽく > 汎用サーバのフレームワーク

S

C CC

ここ

Page 24: Elixirだ 第3回

雰囲気を感じてくれdefmodule Uuid.Server do use GenServer

def start_link do GenServer.start_link(__MODULE__, 0) end

def next(pid) do GenServer.call(pid, :next) end

def handle_call(:next, _from, current) do {:reply, current, current+1} end end

Page 25: Elixirだ 第3回

雰囲気を感じてくれiex(1)> alias Uuid.Server, as: S

iex(2)> {:ok, pid} = S.start_link

iex(3)> S.next(pid) iex(4)> S.next(pid) iex(5)> S.next(pid)

Page 26: Elixirだ 第3回

defmodule Uuid.Server do use GenServer

def start_link do GenServer.start_link(__MODULE__, 0) end

def next(pid) do GenServer.call(pid, :next) end

def handle_call(:next, _from, current) do {:reply, current, current+1} end end

雰囲気を感じてくれクライアントが使う関数

サーバのコールバック関数

Page 27: Elixirだ 第3回

defmodule Uuid.Server do use GenServer

def start_link do GenServer.start_link(__MODULE__, 0) end

def next(pid) do GenServer.call(pid, :next) end

def handle_call(:next, _from, current) do {:reply, current, current+1} end end

雰囲気を感じてくれinit/1に渡される引数       ↓

↑対応関係 ↓

  ↑ 返却値

↑ 次の状態

今の状態  ↓

      ↑ このモジュール = Uuid.Server

Page 28: Elixirだ 第3回

コールバックコールバック クライアントの所業 役割

init/1 GenServer.start_link/2 GenServer.start/2 初期化

handle_call/3 GenServer.call/2 同期処理

handle_cast/2 GenServer.cast/2 非同期処理

handle_info/2 send(pid, msg) その他の メッセージを処理

code_change/3 - コードリロード

terminate/2 - 終了処理

Page 29: Elixirだ 第3回

戻り値> init/1

> {:ok, state} > {:ok, state, timeout}

>時間内にリクエストが来ないと:timeoutが来る > handle_infoで捌く

> :ignore > {:stop, reason}

> terminate/2 > :ok

Page 30: Elixirだ 第3回

戻り値> handle_call/3 > {:reply, reply, new_state} > {:reply, reply, new_state, timeout} > {:noreply, new_state} > {:noreply, new_state, timeout} > {:stop, reason, new_state} > {:stop, reason, reply, new_state}

Page 31: Elixirだ 第3回

戻り値> handle_cast/2 > {:noreply, new_state} > {:noreply, new_state, timeout} > {:stop, reason, new_state}

Page 32: Elixirだ 第3回

戻り値> handle_info/2 > {:noreply, new_state} > {:noreply, new_state, timeout} > {:stop, reason, new_state}

> code_change/3 > {:ok, new_state} > {:error, reason}

Page 33: Elixirだ 第3回

behaviour> Javaで言うInterface > 実装すべき関数をユーザに示す > デフォルト実装の提供も可 > GenServerのコールバックもこれで > 使い方は割愛…

イギリス綴りに注意

Page 34: Elixirだ 第3回

GenServerでスタック> StackServer.start_link >空スタックをつくる

> StackServer.push(pid, elem) >要素を先頭に追加

> StackServer.pop(pid) >要素を取り出す > {:ok, item}を返す

Page 35: Elixirだ 第3回

defmodule StackServer do use GenServer

def start_link do GenServer.start_link(__MODULE__, []) end

def push(pid, elem) do GenServer.cast(pid, {:push, elem}) end

def pop(pid) do GenServer.call(pid, :pop) end

スタック

Page 36: Elixirだ 第3回

def init(_) do {:ok, []} end

def handle_cast({:push, e}, stack) do {:noreply, [e | stack]} end

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

スタック

Page 37: Elixirだ 第3回

AgentとTask> GenServerの2つの側面 > 状態保持 > (並列)計算処理

> 状態保持が主 → Agent > 計算処理が主 → Task

迷ったらGenServerという選択もアリだと思います

Page 38: Elixirだ 第3回

Agentモジュール> 状態保持を抽象化 > 開始:start_link/2 など > 終了:stop/2 > 取得:get/3 など > 更新:update/3 など

> 複数プロセス間でmapを共有する など

Page 39: Elixirだ 第3回

例:mapを保持{:ok, pid} = Agent.start_link fn -> %{} end

Agent.update pid, fn (map) -> Map.put(map, :key, :val) end

Agent.get pid, fn (map) -> Map.get(map, :key) end

Page 40: Elixirだ 第3回

Taskモジュール> プロセスに仕事をさせる > 仕事開始:async/1 > 終了待機:await/1

Page 41: Elixirだ 第3回

def para do [ fn -> :timer.sleep 1000 end, fn -> :timer.sleep 1000 end, fn -> :timer.sleep 1000 end, fn -> :timer.sleep 1000 end ] |> Enum.map(&Task.async/1) |> Enum.map(&Task.await/1) end

例:1秒で

Page 42: Elixirだ 第3回

気が向いたら> StackServer > 関数の追加 > スタックを空にするclear/1 > スタックをリストで返すto_list/1 > スタック長を返すlength/1

> 空の状態でpopされたら{:error, :empty} > スタック長の制限 > Agentを使って再実装

Page 43: Elixirだ 第3回

今回やったこと> Erlangプロセス > メッセージング > 状態を持ったプロセス > GenServer > Agent > Task