Slide 1

Slide 1 text

Elixir はじめての 並⾏プログラミング

Slide 2

Slide 2 text

ElixirにはErlang VM特徴である軽量プロセスを⽣か した、並⾏プログラミングを簡単に⾏う仕組みが整備 されています。 Supervisor GenServer Flow Task Agent

Slide 3

Slide 3 text

今回は⼀番簡単な Task のお話です

Slide 4

Slide 4 text

まずはiexで対話環境を実⾏。 引数なしで1を返すだけの関数を、タスクに登録。 iex(1)> task_id = Task.async(fn -> 1 end) %Task{ owner: #PID<0.272.0>, pid: #PID<0.2398.0>, ref: #Reference<0.4175548739.2466250754.158616> } タスクIDにはTask構造体が帰ってきます。 iex(1)> Task.await(task_id) 1 task_idを⼿掛かりに値を取り出すことができまし た。

Slide 5

Slide 5 text

5秒待って1返す関数を登録します iex(1)> task_id = Task.async(fn -> Process.sleep(5000); \ 1 end) iex(2)> Task.await(task_id) 1 (1)の後(2)を即実⾏すれば数秒待って、1が帰ってき ます。 JavascriptのPromise/thenやasync/awaitと同じイメ ージです。 ただし、Elixirではプロセスにタスクを任せていま す。

Slide 6

Slide 6 text

1から10までの数字を5秒後それぞれを返すタスク を⽣成 iex> tasks = 1..10 \ |> Enum.map(fn i -> \ Task.async(fn -> Process.sleep(5000);\ i end) \ end) タスクidのリストがすぐに戻ってくるので map関数で取り出します。 iex> tasks |> Enum.map( &Task.await &1 ) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 10個のタスクを同時に捌くことができました。

Slide 7

Slide 7 text

今度は10000個のタスクを⽴ち上げてみます iex> tasks = 1..10_000 \ |> Enum.map(fn i -> \ Task.async(fn -> Process.sleep(5000);\ i end) \ end) タスク登録に1秒かかりません。 iex> tasks |> Enum.map( &Task.await &1 ) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37 43, 44, 45, 46, 47, 48, 49, 50, ...] 即レスポンスが帰ってきます。

Slide 8

Slide 8 text

このように、⼤量のプロセスに関数を当ての⾮同期処 理が簡単にできます。もちろん、CPUのコア数以上 のプロセスを割り当てても速度は上がりません。 Tips: 当然ながら、値を取り出したタスクは not alive です iex> Process.info List.first(tasks).pid # キレてますか︖ nil # キレてます Task.awaitのデフォルトTimeout5秒です iex> Task.await( task_id, 10_000 ) # 第⼆引数でTimeout指定

Slide 9

Slide 9 text

APIの場合はどうか Gourmet APIを並列で叩く︕

Slide 10

Slide 10 text

APIを使うライブラリ

Slide 11

Slide 11 text

準備 プロジェクト作成 iex phx.new gourumet --no-ecto --no-brunch cd gorumet mix.exs def deps do [{ ︓httpoison、"〜> 1.0 " }] #追加 end mix deps.get

Slide 12

Slide 12 text

各サービスへのAPI I/FをHttpoisonで実装しました ぐるナビのAPIサンプル(Gist) ホットペッパーAPIサンプル(Gist) libの配下に上記を配置、Api Key関連を直接埋め込む なりすれば、すぐに利⽤可能

Slide 13

Slide 13 text

例) ぐるなびAPIで中央区にある焼き⿃屋を検索 GuruNavi.get_shops( "福岡市中央区", "焼き⿃" ) 同じくHotPapper HotPepperApi.get_shops({"福岡市中央区","焼き⿃"})

Slide 14

Slide 14 text

2つのサービスを呼び出して結果をまとめます iex> shops = [ GuruNaviApi.get_shops({"福岡市中央区","ベトナム料理"}), HotPepperApi.get_shops({"福岡市中央区","ベトナム料理"}) ] iex> shops |> Enum.map(fn r -> r[:shops] end) |> List.flatten |> Enum.sort( fn {a, _, _, _}, {b, _, _, _} -> a<=b end ) [ {"Part du monde ", "050-3461-6009", "ハンモックカフェ", " 12:00〜20:30(L.O.20:00)"}, {"Xinchao ", "050-3373-1681", "ベトナム居酒屋", "⽉〜⽇ ランチ︓11:00〜14:00
⽉〜⽇ ディナー︓18:00〜23:00" {"ゴンゴン ngon ngon", "N/A", "ベトナム料理", "⽕〜⽇、祝⽇、祝前⽇: 12:00〜15:00 (料理L.O. 14:30)17:30〜23:00

Slide 15

Slide 15 text

APIを並列実⾏する

Slide 16

Slide 16 text

以下は定義した時点で、APIが逐次実⾏されるので shops = [ GuruNaviApi.get_shops({"福岡市中央区","ベトナム料理"}), HotPepperApi.get_shops({"福岡市中央区","ベトナム料理"}) ] 匿名関数のリストに置き換えます。 addr_a = "福岡市中央区" addr_b = "福岡市博多区" # 博多区参戦! dish = "ベトナム料理" reqs = [ fn -> GuruNaviApi.get_shops({addr_a, dish}) end, fn -> HotPepperApi.get_shops({addr_a, dish}) end, fn -> GuruNaviApi.get_shops({addr_b, dish}) end, fn -> HotPepperApi.get_shops({addr_b, dish}) end, ]

Slide 17

Slide 17 text

Taskに投げて並⾏リクエストを⾏います。 shops = reqs |> Enum.map( &Task.async(&1) ) |> Enum.map( &Task.await(&1) ) ⼊れ⼦のリストを平たくして、ソート List.flatten(shops) |> Enum.sort( fn {a,_,_,_},{b,_,_,_} -> a < b end ) [ {"モン アン エスニック ", "092-722-6860", "ベトナム料理", %{}}, {"ベトナム料理SAI‐GON ", "092-721-1284", "ベトナム料理", %{}}, {"ベトナムビストロ asiatico ", "092-725-6684", "貸切、⼥⼦会、ワイン", "⽕〜⾦ ランチ︓12:00〜15:00
⼟・⽇・祝⽇ 12:00〜17:00(※平⽇・⼟ {"ベトナムカフェレストラン ゴンゴン ", "092-403-6689", "ベトナム料理", %{}},

Slide 18

Slide 18 text

グルメAPIのマッシュアップ完了 簡単に並列処理が書ける︕ HTTPoisonでAPIリクエスト 匿名関数でリクエストをリスト化 Task.async&awaitで簡単に並列実⾏

Slide 19

Slide 19 text

Enjoy Parallel Programming!