■ ElixirのConcurrent Programmingについてまとめた過去記事
Elixir Concurrent Programming(1) - Spawn - Qiita
Elixir Concurrent Programming(2) - GenServer - Qiita
Elixir Concurrent Programming(3) - Supervisors - Qiita
Elixir Concurrent Programming(4) - Task and Agent -Qiita
SpawnやGenServerなどより、簡単にバックグランド・プロセスをつくるための仕組みである、TaskとAgentについてみてみます。
1.Task
GenServerはサーバとして働き、永続的にリクエストを受け付け、処理を行うというものでした。これに反してTaskはworkerプロセスとして起動され、リクエストを受け取ることもなく、与えられた仕事が終了したら停止しするものです。具体的には、Taskは関数をバックグラウンドのプロセスとして走らせます。Taskには2種類あって、resultを返すものと返さないものがあります。前者をawaited taskと呼びます。
(補足)ここで述べるのは基本的な事柄ですので必要に応じて「Task - Hexdocs」を参照してください。
awaited taskはTask.asyncで起動し、Task.awaitで返答を受け取ります。
以下に簡単な例を示します。特に次のことに注目します。Task.awaitの順番に関して、worker40とworker37を入れ替えます。当然、worker37の方が完了が早いはずですが、上から順番に実行されるので、worker40のawaitを待ってworker37のawaitが実行されます。
defmodule Fib do
def fib(0), do: 0
def fib(1), do: 1
def fib(n), do: Fib.fib(n-1) + Fib.fib(n-2)
end
### 関数Fib.fibに引数を与えて起動します
worker35 = Task.async(fn -> Fib.fib(35) end)
worker37 = Task.async(fn -> Fib.fib(37) end)
worker40 = Task.async(fn -> Fib.fib(40) end)
### Task.awaitはworkerの仕事の完了を待ちます。
IO.puts "worker35を待つ"
result35 = Task.await(worker35,50000) ### timeout=500000
IO.puts "worker35の結果 = #{result35}"
IO.puts "worker40を待つ"
result40 = Task.await(worker40,50000) ### timeout=500000
IO.puts "worker40の結果 = #{result40}"
IO.puts "worker37を待つ"
result37 = Task.await(worker37,50000) ### timeout=500000
IO.puts "worker37の結果 = #{result37}"
以下が実行結果です。
# elixir mytask.ex
worker35を待つ ### ちょっと待って表示されます
worker35の結果 = 9227465
worker40を待つ ### 結構待ちます
worker40の結果 = 102334155
worker37を待つ ### 既に結果が出ているのですぐ表示されます
worker37の結果 = 24157817
Non-awaited taskはTask.asyncでなく、Task.start_linkで呼び出されます。簡単に以下のような感じです。
iex(1)> Task.start_link(fn -> IO.puts("Something done!") end)
Something done!
{:ok, #PID<0.86.0>}
【2022/10/23】Task.async を使った負荷試験ツールを見つけました
Elixirでパラレルな負荷試験ツールを作る
2.Agent
Agentはstateを持ったバックグラウンドのプロセスです。stateは、あるプロセスやnode内のいろいろな場所からアクセスされます。他のnodeからアクセスされることもあります。ザックリ言えばGenServerをお手軽に使うイメージですかね。
(補足)ここで述べるのは基本的な事柄ですので必要に応じて「Agent - Hexdocs」を参照してください。
簡単な使い方です。
# iex
iex(1)> {:ok, pid} = Agent.start_link(fn -> %{name: "Yamada", age: 30} end)
{:ok, #PID<0.86.0>}
iex(2)> Agent.get(pid, fn state -> state.name end)
"Yamada"
iex(3)> Agent.update(pid, fn state -> %{state | age: state.age + 1} end)
:ok
iex(4)> Agent.get(pid, fn state -> state end)
%{age: 31, name: "Yamada"}
今回は以上です。