Elixir-processes
Elixir-プロセス
Elixirでは、すべてのコードはプロセス内で実行されます。 プロセスは互いに分離され、互いに並行して実行され、メッセージパッシングを介して通信します。 Elixirのプロセスをオペレーティングシステムのプロセスと混同しないでください。 Elixirのプロセスは、メモリとCPUの点で非常に軽量です(他の多くのプログラミング言語のスレッドとは異なります)。 このため、数万または数十万ものプロセスが同時に実行されることも珍しくありません。
この章では、新しいプロセスを生成し、異なるプロセス間でメッセージを送受信するための基本的な構成について学習します。
スポーン関数
新しいプロセスを作成する最も簡単な方法は、 spawn 関数を使用することです。 spawn は、新しいプロセスで実行される関数を受け入れます。 たとえば-
pid = spawn(fn -> 2 * 2 end)
Process.alive?(pid)
上記のプログラムが実行されると、次の結果が生成されます-
false
spawn関数の戻り値はPIDです。 これはプロセスの一意の識別子であるため、PIDの上でコードを実行すると、異なるコードになります。 この例でわかるように、プロセスが生きているかどうかを確認すると、プロセスは停止しています。 これは、指定された関数の実行が終了するとすぐにプロセスが終了するためです。
既に述べたように、すべてのElixirコードはプロセス内で実行されます。 自己機能を実行すると、現在のセッションのPIDが表示されます-
pid = self
Process.alive?(pid)
上記のプログラムを実行すると、次の結果が生成されます-
true
メッセージの受け渡し
メッセージを send でプロセスに送信し、 receive で受信できます。 現在のプロセスにメッセージを渡し、同じプロセスで受信してみましょう。
send(self(), {:hello, "Hi people"})
receive do
{:hello, msg} -> IO.puts(msg)
{:another_case, msg} -> IO.puts("This one won't match!")
end
上記のプログラムが実行されると、次の結果が生成されます-
Hi people
send関数を使用して現在のプロセスにメッセージを送信し、selfのPIDに渡しました。 次に、 receive 関数を使用して着信メッセージを処理しました。
メッセージがプロセスに送信されると、メッセージは*プロセスメールボックス*に保存されます。 受信ブロックは、現在のプロセスメールボックスを通過して、指定されたパターンのいずれかに一致するメッセージを検索します。 受信ブロックは、ガードや、caseなどの多くの句をサポートしています。
パターンのいずれかに一致するメールボックスにメッセージがない場合、現在のプロセスは一致するメッセージが到着するまで待機します。 タイムアウトも指定できます。 例えば、
receive do
{:hello, msg} -> msg
after
1_000 -> "nothing after 1s"
end
上記のプログラムが実行されると、次の結果が生成されます-
nothing after 1s
注-メッセージがすでにメールボックスにあると予想される場合、0のタイムアウトを指定できます。
リンク集
Elixirでの最も一般的なスポーン形式は、実際には spawn_link 関数によるものです。 spawn_linkの例を見る前に、プロセスが失敗したときに何が起こるかを理解しましょう。
spawn fn -> raise "oops" end
上記のプログラムを実行すると、次のエラーが生成されます-
[error] Process #PID<0.58.00> raised an exception
** (RuntimeError) oops
:erlang.apply/2
エラーをログに記録しましたが、生成プロセスはまだ実行中です。 これは、プロセスが分離されているためです。 あるプロセスの失敗を別のプロセスに伝播させるには、それらをリンクする必要があります。 これは、 spawn_link 関数を使用して実行できます。 同じことを理解するための例を考えてみましょう-
spawn_link fn -> raise "oops" end
上記のプログラムを実行すると、次のエラーが生成されます-
* *(EXIT from #PID<0.41.0>) an exception was raised:
* * (RuntimeError) oops
:erlang.apply/2
これを iex シェルで実行している場合、シェルはこのエラーを処理し、終了しません。 ただし、最初にスクリプトファイルを作成してから elixir <file-name> .exs を使用して実行した場合、このエラーにより親プロセスも停止します。
フォールトトレラントシステムを構築するとき、プロセスとリンクは重要な役割を果たします。 Elixirアプリケーションでは、しばしばプロセスをスーパーバイザーにリンクします。スーパーバイザーは、プロセスが終了したときを検出し、その場所で新しいプロセスを開始します。 これは、プロセスが分離されており、デフォルトでは何も共有しないためにのみ可能です。 プロセスは分離されているため、プロセスの障害がクラッシュしたり、別のプロセスの状態を破壊したりすることはありません。 他の言語では、例外をキャッチ/処理する必要があります。 Elixirでは、スーパーバイザーがシステムを適切に再起動することを期待しているため、実際にプロセスを失敗させても問題ありません。
状態
たとえば、アプリケーション構成を保持するために状態を必要とするアプリケーションを構築する場合、またはファイルを解析してメモリに保持する必要がある場合、どこに保存しますか? Elixirのプロセス機能は、このようなことを行うときに便利です。
無限にループし、状態を維持し、メッセージを送受信するプロセスを作成できます。 例として、 kv.exs という名前のファイルにキーと値のストアとして機能する新しいプロセスを開始するモジュールを作成してみましょう。
defmodule KV do
def start_link do
Task.start_link(fn -> loop(%{}) end)
end
defp 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
*start_link* 関数は、空のマップで始まる *loop* 関数を実行する新しいプロセスを開始することに注意してください。 次に、 *loop* 関数はメッセージを待機し、各メッセージに対して適切なアクションを実行します。 *:get *メッセージの場合、メッセージを発信者に送り、ループを再度呼び出して新しいメッセージを待ちます。 *:put *メッセージは、指定されたキーと値が保存されたマップの新しいバージョンで実際に *loop* を呼び出します。
私たちは今、次を実行しましょう-
iex kv.exs
これで、 iex シェルになっているはずです。 私たちのモジュールをテストするには、次を試してください-
{:ok, pid} = KV.start_link
# pid now has the pid of our new process that is being
# used to get and store key value pairs
# Send a KV pair :hello, "Hello" to the process
send pid, {:put, :hello, "Hello"}
# Ask for the key :hello
send pid, {:get, :hello, self()}
# Print all the received messages on the current process.
flush()
上記のプログラムが実行されると、次の結果が生成されます-
"Hello"