膝のアーランクラスター

「まれな」アーラン言語に関する記事カウンターをもう1つ増やすために、アーランクラスターをひざの上で組み立てて、並列コンピューティングを実行する方法を説明します。







何が起こっているのかを理解するには、読者はErlangの基本(OTPなし)を知る必要があるかもしれません。 ちなみに、これは入手可能 レジを離れることなく 居心地の良いHabrを離れることなく、ここに:









しかし、事前にスマートになってはいけません...







叙情的な余談



現在理解しているように、IT業界の発展につれて、まったく異なるプログラミング言語がアプリケーションを見つけています。 Erlang言語は例外ではなく、もともとはフォールトトレラントシステム用の言語として開発されましたが、分散マルチプロセッサシステムの世界でニッチを獲得した理由は次のとおりです。









そして、残りながら:









本番環境でErlangを使用している企業とプロジェクトをリストします。









私たちの意見では、言語には弱点があることを読者から隠しません。弱点は、他の著者によってよく説明されています。たとえば、 Erlang in Wargaming







それでは、プログラムを作成して、それを行う関数を記述しましょう...







関数値の並列計算(同時に実行されるプロセスの数と計算タイムアウトの制限付き)



マップ機能


この関数の署名をlists:map



似たものにしlists:map









map(Fn, Items, WorkersN, Timeout) -> {ok, [FnResult]} | {error, timeout}, throws {'EXIT', Reason}



map(Fn, Items, WorkersN, Timeout) -> {ok, [FnResult]} | {error, timeout}, throws {'EXIT', Reason}



、ここで:









関数の実装:







 -module(dmap_pmap). ... map(Fn, Items, WorkersN, Timeout) -> Total = length(Items), Context = #{fn => Fn, items => Items, results => #{}, counter => 1, total => Total, workers => 0, workers_max => min(WorkersN, Total), pids => #{}, timeout => Timeout}, Self = self(), spawn(fun() -> Self ! map_loop(Context) end), Result = receive Any -> Any end, case get_error(Result) of undefined -> {ok, Result}; {'EXIT', Reason} -> throw({'EXIT', Reason}); {error, timeout} -> {error, timeout} end.
      
      





コードのハイライトは次のとおりです。









map_loop関数


この関数のシグネチャ: map_loop(Context) -> [FnResult]









実装:







 map_loop(#{counter := Counter, total := Total, workers := Workers, workers_max := WorkersMax, fn := Fn, items := Items, pids := PIDs} = Context) when Workers < WorkersMax, Counter =< Total -> Self = self(), Index = Counter, WorkerIndex = Workers + 1, PID = spawn(fun() -> WorkerPID = self(), io:fwrite("{Index, PID, {W, WMax}}: ~p~n", [{Index, WorkerPID, {Workers + 1, WorkersMax}}]), Item = lists:nth(Index, Items), Self ! {Index, WorkerPID, catch Fn(Item, WorkerIndex)} end), Context2 = Context#{counter => Counter + 1, workers => Workers + 1, pids => PIDs#{PID => Index}}, map_loop(Context2); map_loop(#{workers := Workers, timeout := Timeout, pids := _PIDs} = Context) when Workers > 0 -> receive {Index, PID, {'EXIT', _Reason} = Result} when is_integer(Index) ->%% error case io:fwrite("got error: ~p~n", [{Index, PID, Result}]), Context2 = set_worker_result(PID, {Index, Result}, Context), Context3 = kill_workers(Context2, error), create_result(Context3); {Index, PID, Result} when is_integer(Index) -> %% ok case io:fwrite("got result: ~p~n", [{Index, PID, Result}]), Context2 = set_worker_result(PID, {Index, Result}, Context), map_loop(Context2) after Timeout -> %% timeout case io:fwrite("timeout: ~p~n", [#{context => Context}]), Context3 = kill_workers(Context, {error, timeout}), create_result(Context3) end; map_loop(#{workers := Workers, pids := PIDs} = Context) when Workers == 0, PIDs == #{} -> create_result(Context).
      
      





実装を見ていきましょう。









ここで使用したプライベート関数について見ていきましょう。









関数の完全なリストはGitHubで見ることができます: ここ







テスト中


ここで、Erlang REPLを使用して関数を少しテストします。







1)2番目のワーカーの結果が最初のワーカーよりも早くなるように、2人のワーカーに対して計算を実行します。







>catch dmap_pmap:map(fun(1, _) -> timer:sleep(2000), 1; (2, _) -> timer:sleep(1000), 2 end, [1, 2], 2, 5000).









最後の行は計算の結果です。







 {Index, PID, {W, WMax}}: {1,<0.1010.0>,{1,2}} {Index, PID, {W, WMax}}: {2,<0.1011.0>,{2,2}} got result: {2,<0.1011.0>,2} got result: {1,<0.1010.0>,1} {ok,[1,2]}
      
      





2)2人のワーカーの計算を実行して、最初のワーカーでクラッシュが発生するようにします。







>catch dmap_pmap:map(fun(1, _) -> timer:sleep(100), erlang:exit(terrible_error); (2, _) -> timer:sleep(100), 2 end, [1, 2], 2, 5000).









 {Index, PID, {W, WMax}}: {1,<0.2149.0>,{1,2}} {Index, PID, {W, WMax}}: {2,<0.2150.0>,{2,2}} got error: {1,<0.2149.0>,{'EXIT',terrible_error}} kill: <0.2150.0> {'EXIT',terrible_error}
      
      





3)関数の計算時間が許容タイムアウトを超えるように、2人のワーカーに対して計算を実行します。







> catch dmap_pmap:map(fun(1, _) -> timer:sleep(2000), erlang:exit(terrible_error); (2, _) -> timer:sleep(100), 2 end, [1, 2], 2, 1000).









 {Index, PID, {W, WMax}}: {1,<0.3184.0>,{1,2}} {Index, PID, {W, WMax}}: {2,<0.3185.0>,{2,2}} got result: {2,<0.3185.0>,2} timeout: #{context => #{counter => 3,fn => #Fun<erl_eval.12.99386804>, items => [1,2], pids => #{<0.3184.0> => 1}, results => #{2 => 2}, timeout => 1000,total => 2,workers => 1,workers_max => 2}} kill: <0.3184.0> {error,timeout}
      
      





そして最後に...







クラスターコンピューティング



テスト結果は妥当に見えますが、クラスターがそれとどう関係しているのか、熱心な読者が尋ねるかもしれません。

実際、クラスターでのコンピューティングを開始するために必要なほぼすべてのものがすでにあることがわかります。クラスターとは、関連するErlangノードのセットを意味します。







別のdmap_dmap



モジュールにdmap_dmap



、次のシグネチャdmap_dmap



持つ別の関数があります。







map({M, F}, Items, WorkersNodes, Timeout) -> {ok, [FnResult]} | {error, timeout}, throws {'EXIT', Reason}



map({M, F}, Items, WorkersNodes, Timeout) -> {ok, [FnResult]} | {error, timeout}, throws {'EXIT', Reason}



、ここで:









実装:







 -module(dmap_dmap). ... map({M, F}, Items, WorkersNodes, Timeout) -> Fn = fun(Item, WorkerIndex) -> Node = lists:nth(WorkerIndex, WorkersNodes), rpc:call(Node, M, F, [Item]) end, dmap_pmap:map(Fn, Items, length(WorkersNodes), Timeout).
      
      





この関数のロジックは非常に単純です。前のセクションのdmap_pmap:map



関数を使用します。この関数に匿名関数を代入し、単純に目的のノードで計算を実行します。







テストのために、別のモジュールで、ノードの名前を返す関数を開始します。







 -module(dmap_test). test(X) -> {ok, {node(), X}}.
      
      





テスト中



テストのために、2つのターミナルでノードを実行する必要があります。たとえば、次のように(プロジェクトの作業ディレクトリから):







make run NODE_NAME=n1@127.0.0.1









make run NODE_NAME=n2@127.0.0.1









最初のノードで計算を実行します。







(n1@127.0.0.1)1> dmap_dmap:map({dmap_test, test}, [1, 2], ['n1@127.0.0.1', 'n2@127.0.0.1'], 5000).









そして、結果が得られます。







 {Index, PID, {W, WMax}}: {1,<0.1400.0>,{1,2}} {Index, PID, {W, WMax}}: {2,<0.1401.0>,{2,2}} got result: {1,<0.1400.0>,{ok,{'n1@127.0.0.1',1}}} got result: {2,<0.1401.0>,{ok,{'n2@127.0.0.1',2}}} {ok,[{ok,{'n1@127.0.0.1',1}},{ok,{'n2@127.0.0.1',2}}]}
      
      





置き換えることができるように、注文したとおり、結果は2つのノードから届きました。







結論の代わりに



私たちの簡単な例は、アプリケーションの分野で、Erlangが有用な問題を解決することを比較的簡単にしていることを示しています(他のプログラミング言語を使用して解決するのはそれほど簡単ではありません)。







記事の形式が短いため、ライブラリのコードとアセンブリに関する質問があるかもしれませんが、それらは舞台裏に残されています。







GitHubの詳細については、 こちらをご覧ください







次の記事で残りの詳細をカバーすることをお約束します。








All Articles