Elixirでは、すべてのコードはプロセスの中で実行されます。
デフォルトでは、関数は呼び出されたプロセスと同じプロセスで実行されます。ある関数を新しいプロセスで明示的に実行したいときは、spawn/1を使います。
spawn(fn -> 2 + 2 end)
# => #PID<0.125.0>
spawn/1は、与えられた0引数の関数を実行する新しいプロセスを作り、プロセス識別子(PID)を返します。新しいプロセスは、その関数が実行されている間は生き続け、その後は静かに終了します。
Elixirのプロセスは、オペレーティングシステムのプロセスと混同しないでください。Elixirのプロセスは、はるかに少ないメモリとCPUしか使いません。何百ものElixirプロセスを動かすElixirアプリケーションがあっても、まったく問題ありません。
プロセス同士は、情報を直接共有することはありません。データを共有するために、プロセスは_メッセージを送受信_します。
send/2を使えば、どんなプロセスにもメッセージを送ることができます。send/2の最初の引数は受信者のPIDで、2番目の引数がメッセージです。
メッセージはどんな型でもかまいません。多くの場合、アトムとタプルでできています。返事を受け取りたいときは、メッセージのどこかに送信者のPIDを含めておきます。現在のプロセスのPIDはself()で取得できます。
send/2は、メッセージが受信者に届いたかどうかも、受信者がまだ生きているかどうかも確認しません。メッセージは受信者の_メールボックス_に入り、受信者が明示的に_メッセージを受信_しようとしたときにだけ読み出されます。
メッセージは、receive/1マクロを使ってメールボックスから読み出すことができます。receive/1は、メッセージをパターンマッチできるdoブロックを受け取ります。
receive do
{:ping, sender_pid} -> send(sender_pid, :pong)
:do_nothing -> nil
end
receive/1は、与えられたパターンのいずれかにマッチするメッセージを_1つ_メールボックスから取り出し、そのパターンに対応する式を実行します。メールボックスにメッセージがない場合、またはどのメッセージもどのパターンにもマッチしない場合、receive/1はメッセージを待ちます。
複数のメッセージを受け取りたいときは、receive/1を再帰的に呼び出す必要があります。たとえばloopという名前の再帰関数を実装し、それがreceive/1を呼び出し、メッセージに対して何か処理をしてから、さらにメッセージを待つために自分自身を呼び出す、というのはよくあるパターンです。receive/1の呼び出しから次の呼び出しへ状態を持ち越したいときは、そのloop関数に引数を渡すことで実現できます。
プロセス識別子は、それ自体が1つのデータ型です。これは_メールボックスのアドレス_として機能します。プロセスのPIDがわかれば、そのプロセスにメッセージを送ることができます。PIDは通常、spawnのように新しいプロセスを作る関数の戻り値として、間接的に生成されます。
Take-A-Numberマシン用の組み込みシステムを書きます。とてもシンプルなモデルで、連続した番号を発行し、最後に発行した番号を報告することができます。
start/0関数を実装しましょう。新しいプロセスを起動し、そのプロセスのPIDを返します。新しいプロセスは、まだ何もする必要はありません。
TakeANumber.start()
# => #PID<0.138.0>
このコードを実行するたびに、PIDが変わる可能性がある点に注意してください。
マシンを変更して、新しく起動したプロセスが、初期状態0でメッセージを受信できるように(_受信ループ_を開始するように)します。{:report_state, sender_pid}というメッセージを受信できるようにしましょう。これらのメッセージへの応答として、現在の状態(最後に発行したチケット番号)をsender_pidに送信し、その後さらにメッセージを待ちます。
machine_pid = TakeANumber.start()
# a client sending a message to the machine
send(machine_pid, {:report_state, self()})
# a client receiving a message from the machine
receive do
msg -> msg
end
# => 0
マシンを変更して、{:take_a_number, sender_pid}というメッセージを受信できるようにします。状態を1増やし、新しい状態をsender_pidに送信してから、さらにメッセージを待ちます。
machine_pid = TakeANumber.start()
# a client sending a message to the machine
send(machine_pid, {:take_a_number, self()})
# a client receiving a message from the machine
receive do
msg -> msg
end
# => 1
マシンを変更して、:stopメッセージを受信できるようにします。メッセージの受信待ちをやめます。
マシンを変更して、予期しないメッセージを受信したときはそれを無視し、引き続きメッセージを待つようにします。