今日のElixirSchoolメモ20「並行性」

Elixir Schoolの勉強メモです。

https://elixirschool.com/ja/lessons/intermediate/concurrency

このレッスンはプログラミングElixirだと、第2章並行プログラミングの序盤くらい。

## 概要

Elixirの売りの1つは並行性（コンカレンシー）に対応していること。
Erlang VM(BEAM)のおかげで並行処理は簡単である。

並行性のモデルはアクターに依存している。
アクターとはメッセージパッシングによって他のプロセスと相互通信を行う、制御されたプロセスである。

Elixirでプロセスという言葉を使うとき、オペレーションシステムのプロセスを意味していない。
ElixirはErlangでサポートされているプロセスを使う。

## プロセス

Erlang VM(BEAM)内のプロセスは軽量で、全てのCPU間で実行される。
ネイティブスレッドのように見えるが、それより単純で、Elixirアプリケーション内に数千の並行プロセスを持つことも珍しくない。

新しいプロセスを作る最も簡単な方法は、匿名/名前付き関数を引数に取る`spawn`である。
新しいプロセスが作られると、プロセス識別子（PID）が返る。

```elixir
iex(1)> defmodule Example do
...(1)>   def add(a, b) do
...(1)>     IO.puts(a + b)
...(1)>   end
...(1)> end
iex(2)> Example.add(2, 3)
5
:ok
```

この関数を非同期的に評価するには、`spawn/3`を用いる。

```elixir
iex(4)> spawn(Example, :add, [2, 3])
5
#PID<0.120.0>
```

### メッセージパッシング

相互通信をするのに、プロセスはメッセージパッシングを頼る。
これは2つの主な部品、`send/2`と`receive`がある。
`send/2`関数を使うとメッセージをPID宛に送ることができる。
受信するには`receive`を使ってメッセージをマッチし、マッチするものが見つからない場合は中断されずに実行中のままとなる。

```elixir
iex(6)> defmodule Example do
...(6)>   def listen do
...(6)>     receive do
...(6)>       {:ok, "hello"} -> IO.puts("World")
...(6)>     end
...(6)>
...(6)>     listen()
...(6)>   end
...(6)> end
iex(7)> pid = spawn(Example, :listen, [])
#PID<0.137.0>
iex(8)> send pid, {:ok, "hello"}
World
{:ok, "hello"}
iex(9)> send pid, :ok
:ok
```

`listen/0`関数は再帰的なのは、プロセスで複数のメッセージを扱うため。
再帰がないと、プロセスは初めのメッセージを受取り、処理した後に終了するだろう。

### プロセスのリンク

`spawn`の問題の1つとして知られているのはプロセスが強制終了した場合。
これに備えるためには、複数プロセスを`spawn_link`でリンクする必要がある。
二者間でリンクしたプロセスはプロセス終了の通知をお互いに受け取る。
（どちらのプロセスも終了する）

```elixir
iex(1)> defmodule Example do
...(1)>   def explode, do: exit(:kaboom)
...(1)> end
iex(2)> spawn(Example, :explode, [])
#PID<0.116.0>
iex(3)> spawn_link(Example, :explode, [])
** (EXIT from #PID<0.106.0>) shell process exited with reason: :kaboom
```

リンクしたプロセスに現在のプロセスを強制終了させたくない場合もある。
そのためには`Process.flag/2`を用いて終了を補足する必要がある。
この関数はErlangの`process_flag/2`関数を使う。
終了を補足すると（`trap_exit`が`true`になっている）、
`{:EXIT, from_pid, reason}`というタプルのメッセージとして受け取る。

```elixir
iex(1)> defmodule Example do
...(1)>   def explode, do: exit(:kaboom)
...(1)>
...(1)>   def run do
...(1)>     Process.flag(:trap_exit, true)
...(1)>     spawn_link(Example, :explode, [])
...(1)>
...(1)>     receive do
...(1)>       {:EXIT, _from_pid, reason} -> IO.puts("Exit reason: #{reason}")
...(1)>     end
...(1)>   end
...(1)> end
iex(2)> Example.run
Exit reason: kaboom
:ok
```

### プロセス監視

2つのプロセスをリンクせずに、通知はそのまま維持したい場合は`spawn_monitor`でプロセス監視を用いることができる。
プロセスを監視すると、プロセスが強制終了した際にメッセージを受け取る。
現在のプロセスを強制終了させたり、プロセス終了時に明示的な補足をする必要がない。

```elixir
iex(1)> defmodule Example do
...(1)>   def explode, do: exit(:kaboom)
...(1)>
...(1)>   def run do
...(1)>     {pid, ref} = spawn_monitor(Example, :explode, [])
...(1)>
...(1)>     receive do
...(1)>       {:DOWN, _ref, :process, _from_pid, reason} -> IO.puts("Exit reason: #{reason}")
...(1)>     end
...(1)>   end
...(1)> end
iex(2)> Example.run
Exit reason: kaboom
:ok
```

## Agent

Agentは状態を維持するバックグラウンドプロセス周りを抽象化したもの。
アプリケーションやノードの内部で他のプロセスからAgentに接続することができる。
Agentの状態は関数の戻り値に与えられる。

```elixir
iex(1)> {:ok, agent} = Agent.start_link(fn -> [1, 2, 3] end)
{:ok, #PID<0.108.0>}
iex(2)> Agent.update(agent, fn (state) -> state ++ [4, 5] end)
:ok
iex(3)> Agent.get(agent, &(&1))
[1, 2, 3, 4, 5]
```

Agentに名前をつけると、PIDの代わりに名前で参照することができる。

```elixir
iex(5)> Agent.start_link(fn -> [1, 2, 3] end, name: Numbers)
{:ok, #PID<0.113.0>}
iex(6)> Agent.get(Numbers, &(&1))
[1, 2, 3]
```

## Task

Taskは関数をバックグラウンドで実行し、後でその戻り値を受け取る方法を提供する。
アプリケーションの動作を妨げることなく、実行コストの高い演算を処理するときに役立つ。

```elixir
iex(5)> Agent.start_link(fn -> [1, 2, 3] end, name: Numbers)
{:ok, #PID<0.113.0>}
iex(6)> Agent.get(Numbers, &(&1))
[1, 2, 3]
iex(7)> defmodule Example do
...(7)>   def double(x) do
...(7)>     :timer.sleep(2000)
...(7)>     x * 2
...(7)>   end
...(7)> end
iex(8)> task = Task.async(Example, :double, [2000])
%Task{
  mfa: {Example, :double, 1},
  owner: #PID<0.106.0>,
  pid: #PID<0.127.0>,
  ref: #Reference<0.2747384317.3067936769.254030>
}
iex(9)> Task.await(task)
4000
```