近況報告2015/9/19
ことり「海未ちゃん」
海未「」
ことり「ことり[お祭り編]の覚醒後の絵、見せて?」
海未「」
ことり「じゃあ浴衣の絵でもいいよ?」
海未「」
ことり「3枚ってお願いしたよね?どうして1枚もないのかな?」
海未「・・・業務、多忙につき」
ことり「海未ちゃんの業務って何?」
海未「」
希(ことりちゃんの無垢な笑顔が心に刺さるわ・・・)
凛「こ、ことりちゃん、あの、海未ちゃんそろそろ・・・」
ことり「えっとね、凛ちゃんには聞いてないの」
凛「はうぅ」
希「凛ちゃんが、あっさり轟沈」
凛「希上等兵、凛のことはもういいから、海未ちゃんを助けるにゃ・・・」
希「凛二等兵、その犠牲は無駄にはせんで・・・!」
海未「・・・申し訳、ありません」
ことり「海未ちゃんはことりの前に、まず山内先生とアルパカさんに頭下げるべきなんじゃないかな?」
海未「」
希(メールメール・・・えっと、こうさかほのか、っと)
ことり「まさかかさねちゃん2枚で満足しちゃったの?ううん、そんなはずないよね」
海未「」
希(めーでー、めーでー、今すぐ適当な理由を付けてことりちゃんを呼び出すんや。海未ちゃんの胃と精神が危ない。おーばー)
ことり「海未ちゃんがことりのことを大切に思ってくれてるなら、ラブカストーンと睡眠時間くらい・・・あ、電話」
海未「」
ことり「もしもし・・・穂乃果ちゃん?・・・え、今からご飯?2人で?・・・うん、いいよ~」
希(ナイスや穂乃果ちゃん!)
ことり「じゃあことりは、ほ・の・か・ちゃ・ん・と、ご飯食べに行くから、またね~」
希(しまった、これはこれであかん)
海未「・・・ことりが・・・穂乃果と・・・2人で・・・ううっ」
希「海未ちゃん、気をしっかり持つんや!衛生兵!衛生兵!」
凛「衛生兵なら穂乃果ちゃんとご飯食べに行ったにゃ・・・」
希「うう、端から見てるだけで十二指腸に来るわ・・・なんやこの痴話喧嘩」
本編ここから
海未「ことりによる笑顔の激詰めから一夜明け、どうにか傷の癒えたlily whiteの面々は再び集結しElixirの勉強に勤しむのでした・・・」
希「思い出したらあかん、忘れるんや、海未ちゃん」
凛「え、えーと、凛がGenEventの話するにゃ!」
凛「GenEventは、イベント処理のためのモジュールにゃ。イベントマネージャとイベントハンドラがあって、イベントマネージャにイベントハンドラを登録しておくと、イベントが発生したときにイベントハンドラが実行されるんだよ」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
defmodule Forwarder do use GenEvent def handle_event event, parent do send parent, event {:ok, parent} end end {:ok, manager} = GenEvent.start_link GenEvent.add_handler manager, Forwarder, self() GenEvent.sync_notify manager, "にゃ!" receive do msg -> IO.inspect msg end |
1 2 |
$ elixir lw.exs "にゃ!" |
凛「Forwarderがイベントハンドラにゃ。メッセージを親プロセスに転送するだけの簡単なやつだよ」
海未「似たようなコードを以前見ましたね」
凛「GenEvent.start_link
でイベントマネージャを作って、GenEvent.add_handler
でイベントハンドラとしてForwarderを追加してるにゃ。あとは、GenEvent.sync_notify
でイベントを発生させられるよ」
希「notifyしたやつがForwarderのhandle_eventで送り返されてきてるわけやね」
凛「うん。だからsync_notifyで投げたメッセージがreceiveで受け取れるにゃ」
凛「GenServerのcastとcallみたいに、GenEventにもnotifyとsync_notifyがあって、nofityは非同期、sync_notifyは同期になるにゃ」
海未「使い分けに、ちょっと迷ってしまいますね」
凛「一般的にはsync_notifyの方がおすすめみたいだよ」
凛「前回までで作ってきたスコアマッチメッセージサーバにGenEventを組み込んでみるよ」
凛「bucketの作成と削除のときにメッセージを送るようにしてみるにゃ。まずKV.Registryの実装をこうして・・・」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
defmodule KV.Registry do use GenServer # Client API def start_link manager, opts \\ [] do GenServer.start_link __MODULE__, manager, opts end def lookup server, name do GenServer.call server, {:lookup, name} end def create server, name do GenServer.cast server, {:create, name} end # Server API def init events do names = HashDict.new refs = HashDict.new {:ok, %{names: names, refs: refs, events: events}} end def handle_call {:lookup, name}, _from, state do {:reply, HashDict.fetch(state.names, name), state} end def handle_cast {:create, name}, state do if HashDict.get state.names, name do {:reply, {state.names, state.refs}} else {:ok, bucket} = KV.Bucket.start_link ref = Process.monitor bucket refs = HashDict.put state.refs, ref, name names = HashDict.put state.names, name, bucket GenEvent.sync_notify state.events, {:create, name, bucket} {:noreply, %{state | names: names, refs: refs}} end end def handle_info {:DOWN, ref, :process, _pid, _reason}, state do {name, refs} = HashDict.pop state.refs, ref names = HashDict.delete state.names, name GenEvent.sync_notify state.events, {:exit, name, _pid} {:noreply, %{state | names: names, refs: refs}} end def handle_info msg, state do {:noreply, state} end end |
凛「クライアントコードをこうすると・・・」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
defmodule Forwarder do use GenEvent def handle_event event, parent do send parent, event {:ok, parent} end end defmodule Scorematch do def main args do {:ok, manager} = GenEvent.start_link {:ok, registry} = KV.Registry.start_link manager GenEvent.add_mon_handler manager, Forwarder, self() KV.Registry.create registry, "card1" {:ok, bucket} = KV.Registry.lookup registry, "card1" receive do {:create, "card1", ^bucket} -> IO.puts "create:" <> Kernel.inspect bucket end Agent.stop bucket receive do {:exit, "card1", ^bucket} -> IO.puts "exit:" <> Kernel.inspect bucket end end end |
凛「こうにゃ!」
1 2 3 |
$ ./scorematch create:#PID<0.53.0> exit:#PID<0.53.0> |
海未「確かに、作成と削除のタイミングでメッセージを受信していますね」
凛「まずKV.Registryが、stateの1つとしてイベントマネージャを持ち歩くようにしたんだ」
希「ああそれで、namesとrefsのタプルだったのをマップに変えたんやね」
凛「あとはhandle_castとhandle_infoで、bucket作るときと削除するときにsync_notifyしてるだけ。簡単にゃ!」
凛「最後にイベントストリームというのをやってみるよ。iex使った方が分かりやすいにゃ」
1 2 3 4 5 6 7 8 9 10 11 12 13 |
iex(1)> {:ok, manager} = GenEvent.start_link {:ok, #PID<0.58.0>} iex(2)> stream = GenEvent.stream manager %GenEvent.Stream{manager: #PID<0.58.0>, timeout: :infinity} iex(3)> spawn_link fn -> for x <- stream, do: IO.inspect x end #PID<0.61.0> iex(4)> GenEvent.notify manager, "にっこにっこにー☆" :ok "にっこにっこにー☆" iex(5)> GenEvent.notify manager, "ラブアロー☆シュート!" :ok "ラブアロー☆シュート!" |
凛「イベントハンドラの代わりにね、イベントを待ち続けてるプロセスを用意してるにゃ」
海未「3行目の内包表記のところですね」
凛「でね、2行目でGenEvent.stream
が返すstreamが、Enumerableを実装してるから内包表記に使えるんだけど、これはイベントマネージャで発生したイベントを列挙するんだ」
希「何かイベントがあると、ループが1回回る感じ?」
凛「そんな感じだと思うにゃ。とにかく、このプロセスが動き出すと、あとはnotifyする度にこの内包表記の処理が実行されるんだよ」
海未「・・・確かに、iexでnofityを打つ度に実行されているようです」
希「イベントハンドラを定義しておかなくてもいいのはお手軽やね」
希「次回・・・supervisorとかかな」
海未「また複雑になってきましたが・・・」