Elixir はじめての並列処理 (仮)

Elixir はじめての並列処理 (仮)

9f78e80ce37820e6ce959d2fcb8d67d5?s=128

enpedasi

April 17, 2018
Tweet

Transcript

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

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

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

  4. まずは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を⼿掛かりに値を取り出すことができまし た。
  5. 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ではプロセスにタスクを任せていま す。
  6. 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個のタスクを同時に捌くことができました。
  7. 今度は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, ...] 即レスポンスが帰ってきます。
  8. このように、⼤量のプロセスに関数を当ての⾮同期処 理が簡単にできます。もちろん、CPUのコア数以上 のプロセスを割り当てても速度は上がりません。 Tips: 当然ながら、値を取り出したタスクは not alive です iex> Process.info

    List.first(tasks).pid # キレてますか︖ nil # キレてます Task.awaitのデフォルトTimeout5秒です iex> Task.await( task_id, 10_000 ) # 第⼆引数でTimeout指定
  9. APIの場合はどうか Gourmet APIを並列で叩く︕

  10. APIを使うライブラリ

  11. 準備 プロジェクト作成 iex phx.new gourumet --no-ecto --no-brunch cd gorumet mix.exs

    def deps do [{ ︓httpoison、"〜> 1.0 " }] #追加 end mix deps.get
  12. 各サービスへのAPI I/FをHttpoisonで実装しました ぐるナビのAPIサンプル(Gist) ホットペッパーAPIサンプル(Gist) libの配下に上記を配置、Api Key関連を直接埋め込む なりすれば、すぐに利⽤可能

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

  14. 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<BR>⽉〜⽇ ディナー︓18:00〜23:00" {"ゴンゴン ngon ngon", "N/A", "ベトナム料理", "⽕〜⽇、祝⽇、祝前⽇: 12:00〜15:00 (料理L.O. 14:30)17:30〜23:00
  15. APIを並列実⾏する

  16. 以下は定義した時点で、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, ]
  17. 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<BR>⼟・⽇・祝⽇ 12:00〜17:00(※平⽇・⼟ {"ベトナムカフェレストラン ゴンゴン ", "092-403-6689", "ベトナム料理", %{}},
  18. グルメAPIのマッシュアップ完了 簡単に並列処理が書ける︕ HTTPoisonでAPIリクエスト 匿名関数でリクエストをリスト化 Task.async&awaitで簡単に並列実⾏

  19. Enjoy Parallel Programming!