凛「いよいよ並行プログラミングに突入にゃ!」
海未「Elixirの本領発揮ですね」
凛「まずは基礎知識としてプロセスの説明をするにゃ」
凛「いままで見てきたコードは、ぜんぶ1つのプロセスの中で動いてたんだよ。けどElixirでは、いーっぱいプロセスを作って、同時に処理を動かすことができるにゃ!」
希「psで見れるやつ?」
凛「とは違うにゃ。OSのプロセスとは別物みたいだよ」
凛「とりあえずプロセスを1つ作って動かしてみるにゃ」
1 2 3 |
pid = spawn fn -> IO.puts "ラブアロー☆シュート!" end IO.inspect pid |
1 2 3 |
$ elixir lw.exs ラブアロー☆シュート! #PID<0.51.0> |
凛「spawn
関数を使うと新しいプロセスができて、引数で渡した関数が実行されるんだよ」
希「ここだけ見ると、あまり今までと変わらんね」
凛「まだまだこれからにゃ。このspawn
が返すのはPIDっていうデータ型で、できたプロセスのIDが入ってるんだ。でね、このIDを使ってプロセスにアクセスできるんだよ」
1 2 3 4 |
pid = spawn fn -> IO.puts "ラブアロー☆シュート!" end IO.inspect Process.alive? pid IO.inspect Process.alive? pid |
1 2 3 4 |
$ elixir lw.exs ラブアロー☆シュート! true false |
凛「Process.is_alive?
はプロセスが生きてるかどうかチェックする関数にゃ。ちなみにチェックが2回書いてあるのは、1回目だとまだプロセスが生き残ってることが多いからにゃ」
海未「実行が終了したときが寿命だとすれば、2行目の時点ですでに死んでいないとおかしいのではないでしょうか」
凛「今まで見てきたコードならその通りだけど、新しいプロセスは元のプロセスと並行で動くにゃ」
海未「ということは、2行目の時点ではまだ1行目のプロセスも動いている、ということですか?」
凛「うん。で、3行目くらいまで来るとだいたい実行終了して死ぬんだけど、このへんはPCの環境とかによって変わるからこの例を鵜呑みにしない方がいいにゃ」
希「厳密に見たかったら再帰で終了待機してみてもええかもね」
凛「プロセス同士でメッセージの送り合いができるんだ」
1 2 3 4 5 6 |
parent = self() spawn fn -> send parent, {:hi, "いくよFull Combo!"} end receive do {:hi, msg} -> IO.puts msg end |
1 2 |
$ elixir lw.exs いくよFull Combo! |
凛「新しいプロセスから元のプロセスにメッセージを送ってみたにゃ。send
が送信、receive
で受信だよ」
希「self()
は元のプロセスかな」
凛「現在のプロセスのPIDが取れるにゃ。だから、それを送信先にしてsend
を呼んでるんだよ」
海未「タプルの部分がメッセージの本文、ですか?」
凛「そうだけど、実はここはタプルでなくてもいいにゃ。どんな形でも送れるよ」
海未「receive
のパターンマッチで引っかかればいいのですね」
凛「そういうことにゃ」
凛「send
で送ったメッセージは受信側の”mailbox”に届いて、いつでもreceive
で受け取れるんだよ。receive
はmailboxにマッチするメッセージがなかったら、何か入ってくるまで待機するにゃ」
希「待つ時間って決まってるんやない?ほら、スコアマッチなら30秒とか」
凛「もちろんタイムアウトは指定できるにゃ」
1 2 3 4 5 6 7 8 |
parent = self() spawn fn -> send parent, {:hi, "いくよFull Combo!"} end receive do {:hello, msg} -> IO.puts msg after 30_000 -> IO.puts "メッセージ来ないにゃ・・・" end |
1 2 |
$ elixir lw.exs メッセージ来ないにゃ・・・ |
凛「ところで、プロセスの中で例外吐いて死んでも、他のプロセスは意識的に監視してないと気付かないにゃ」
1 2 |
spawn fn -> raise "ハラショー" end |
1 |
$ elixir lw.exs |
海未「何も出てきませんね」
凛「プロセス同士は独立してるから例外も伝わらないんだよ」
凛「spawn
のかわりにspawn_link
を使うと、プロセス同士につながりができるにゃ」
1 2 |
spawn_link fn -> raise "ハラショー" end |
1 2 3 4 |
$ elixir lw.exs ** (EXIT from #PID<0.48.0>) an exception was raised: ** (RuntimeError) ハラショー lw.exs:1: anonymous fn/0 in :elixir_compiler_0.__FILE__/1 |
凛「こうすると親プロセスと心中できるよ」
希「これ、親プロセス側でrescue
とかcatch
とかした方がええん?」
凛「ううん、これから出てくるOTPとかだと、プロセスを監視して死んだら再起動してくれたりするんだよ。だから、あまり気にしなくていいにゃ」
海未「そういえば、例外処理の時にもそんな話がありましたね」
凛「ここまでで見てきたプロセスは、処理を実行してすぐ死んじゃうだけだったけど、値とか設定をずっと持ってたりしたい場合もあるにゃ」
希「使い捨てじゃなくて、動き続けてほしいわけやね」
凛「そんなときは無限ループすればいいにゃ」
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 |
defmodule KV do def start_link do spawn_link fn -> loop %{} end end def loop map do receive do {:get, key, caller} -> send caller, Map.get(map, key) loop map {:put, key, value} -> loop Map.put map, key, value end end end pid = KV.start_link Process.register pid, :server spawn_link fn -> send :server, {:put, "園田ことり劇場11回", "いくよFull Combo!"} end spawn_link fn -> send :server, {:put, "隣の人フルコンします", "この曲好き!"} end spawn_link fn -> send :server, {:get, "園田ことり劇場11回", self()} receive do value -> IO.puts "園田ことり劇場11回「#{value}」" end end spawn_link fn -> send :server, {:get, "隣の人フルコンします", self()} receive do value -> IO.puts "隣の人フルコンします「#{value}」" end end |
1 2 3 |
$ elixir lw.exs 園田ことり劇場11回「いくよFull Combo!」 隣の人フルコンします「この曲好き!」 |
凛「超簡易スコアマッチメッセージサーバにゃ」
海未「隣の人フルコンします・・・うっ、頭が・・・」
希「あの人と当たるとプレッシャーきついんよなあ・・・」
凛「KVってモジュールが、メッセージを保管するプロセスの実装にゃ。実のところただのマップで、データの入ったマップを引数にして再帰を続けてるんだよ」
海未「このプロセスはずっと動き続けるのですね」
凛「明示的に殺さない限りはずっと動いてるよ。でね、receive
のところを見ると、:getか:putで始まるタプルを受け取るようになってて、これはgetとかputとかいう命令だと思えばいいにゃ」
海未「その命令を含むメッセージを受け取ったら、対応する処理をする、と」
凛「getはマップから対応する値を探してメッセージ送信元に送り返して、putは受け取ったキーと値をマップに入れてるにゃ。つまり、メッセージを通してマップへの出し入れをするプロセスなんだよ」
希「そう考えると大したことしてへんな」
凛「Process.register
はプロセスに名前を付けてるにゃ。PIDじゃなくてここで付けた名前でメッセージを送れるようになるから便利だよ」
凛「最初の2つのプロセスは、サーバに名前と一言を送ってるにゃ」
希「これはサーバのマップに収まるんやね」
凛「その後の2つのプロセスは、名前を指定して一言を取ってきてるにゃ」
希「そうすると、さっきサーバに送ったやつが返ってくると」
凛「実際はこういう場合だと、各プレーヤーに1プロセス割り当てて、他のプレーヤーのメッセージ送信に反応してサーバから通知受け取ったりとかすると思うけど、作り込みすぎるとサンプルコードじゃなくなってくるから自重したにゃ」
海未「大勢で1つのシステムを使うときに有利になるのですね」
凛「Elixirがすごく得意な場面だよ」
希「すごく難しいんかと思ってたらそうでもないんやね」
凛「次回は希ちゃんがOTPの解説を始めてくれるはずにゃ」
希「んー、したら頑張ってみよかな」