92

関数型プログラミングの言語と機能の可視性が高まるのをしばらく見守っています。私はそれらを調べましたが、訴えの理由はわかりませんでした。

それから、最近、CodemashでのKevinSmithの「 BasicsofErlang」プレゼンテーションに参加しました。

私はプレゼンテーションを楽しんで、関数型プログラミングの多くの属性がスレッド化/並行性の問題を回避するのをはるかに簡単にすることを学びました。状態と可変性がないために複数のスレッドが同じデータを変更できないことは理解していますが、Kevin氏は(正しく理解していれば)すべての通信はメッセージを介して行われ、メッセージは同期的に処理されます(同時実行の問題を回避します)。

しかし、私はErlangが非常にスケーラブルなアプリケーションで使用されていることを読みました(Ericssonが最初にErlangを作成した理由のすべて)。すべてが同期処理されたメッセージとして処理される場合、1秒あたり数千の要求を効率的に処理するにはどうすればよいでしょうか。非同期処理に移行し始めたのはそのためではありません。複数のスレッドの操作を同時に実行し、スケーラビリティを実現できるようにするためです。このアーキテクチャは、より安全ですが、スケーラビリティの点で一歩後退しているようです。私は何が欠けていますか?

Erlangの作成者は、並行性の問題を回避するために意図的にスレッドのサポートを避けたことを理解していますが、スケーラビリティを実現するにはマルチスレッドが必要だと思いました。

関数型プログラミング言語は、本質的にスレッドセーフでありながら、どのように拡張できるのでしょうか。

4

8 に答える 8

100

関数型言語は(一般に)変数の変更に依存しません。このため、値が固定されているため、変数の「共有状態」を保護する必要はありません。これにより、プロセッサまたはマシン間でアルゴリズムを実装するために従来の言語が通過しなければならないフープジャンプの大部分が回避されます。

Erlangは、メッセージパッシングシステムを組み込むことで、従来の関数型言語よりもさらに進化しています。このシステムでは、コードの一部がメッセージの受信と送信のみを気にし、全体像を気にすることなく、イベントベースのシステムですべてを操作できます。

これが意味するのは、プログラマーはメッセージが別のプロセッサーまたはマシンで処理されることを(名目上)気にしないということです。メッセージを送信するだけで続行できます。応答を気にする場合は、別のメッセージとして待機します。

この結果、各スニペットは他のすべてのスニペットから独立しています。共有コード、共有状態、および多くのハードウェアに分散できる(または分散できない)メッセージシステムからのすべての対話はありません。

これを従来のシステムと比較してください。「保護された」変数とコード実行の周囲にミューテックスとセマフォを配置する必要があります。スタックを介した関数呼び出しには緊密なバインディングがあります(戻りが発生するのを待ちます)。これらすべてがボトルネックを生み出しますが、Erlangのようなシェアードナッシングシステムではそれほど問題にはなりません。

編集:Erlangは非同期であることも指摘しておく必要があります。あなたはあなたのメッセージを送ります、そして多分/いつか別のメッセージが戻ってきます。か否か。

アウト・オブ・オーダーの実行に関するスペンサーの指摘も重要であり、よく答えられています。

于 2009-01-23T21:07:16.127 に答える
74

メッセージキューシステムは、あなたが読んでいる同期部分である「起動して結果を待つ」効果を効果的に生成するため、クールです。これが信じられないほどすばらしいのは、行を順番に実行する必要がないことです。次のコードを検討してください。

r = methodWithALotOfDiskProcessing();
x = r + 1;
y = methodWithALotOfNetworkProcessing();
w = x * y

methodWithALotOfDiskProcessing() が完了するまでに約 2 秒かかり、methodWithALotOfNetworkProcessing() が完了するまでに約 1 秒かかることを考えてみてください。手続き型言語では、行が順番に実行されるため、このコードの実行には約 3 秒かかります。1 つのリソースをめぐって競合することなく、他のメソッドと同時に実行できるメソッドが完了するのを待つ時間が無駄になっています。関数型言語では、コードの行は、プロセッサがそれらを試行するタイミングを指示しません。関数型言語は、次のようなことを試みます。

Execute line 1 ... wait.
Execute line 2 ... wait for r value.
Execute line 3 ... wait.
Execute line 4 ... wait for x and y value.
Line 3 returned ... y value set, message line 4.
Line 1 returned ... r value set, message line 2.
Line 2 returned ... x value set, message line 4.
Line 4 returned ... done.

それはどれほどクールですか?コードを先に進め、必要な場合にのみ待機することで、待機時間が自動的に 2 秒に短縮されました。:D はい、コードは同期的ですが、手続き型言語とは異なる意味を持つ傾向があります。

編集:

この概念を Godeke の投稿と併せて理解すると、複数のプロセッサ、サーバー ファーム、冗長データ ストアなどを利用することがいかに簡単になるかが容易に想像できます。

于 2009-01-23T21:18:27.040 に答える
16

synchronoussequentialを混同している可能性があります。

erlang の関数の本体は順次処理されています。したがって、Spencer がこの「自動的な効果」について述べたことは、erlang には当てはまりません。ただし、この動作は erlang でモデル化できます。

たとえば、1 行の単語数を計算するプロセスを生成できます。複数の行があるため、行ごとにそのようなプロセスを 1 つ生成し、そこから合計を計算するための回答を受け取ります。

このようにして、「重い」計算を行うプロセスを生成し (利用可能な場合は追加のコアを利用します)、後で結果を収集します。

-module(countwords).
-export([count_words_in_lines/1]).

count_words_in_lines(Lines) ->
    % For each line in lines run spawn_summarizer with the process id (pid)
    % and a line to work on as arguments.
    % This is a list comprehension and spawn_summarizer will return the pid
    % of the process that was created. So the variable Pids will hold a list
    % of process ids.
    Pids = [spawn_summarizer(self(), Line) || Line <- Lines], 
    % For each pid receive the answer. This will happen in the same order in
    % which the processes were created, because we saved [pid1, pid2, ...] in
    % the variable Pids and now we consume this list.
    Results = [receive_result(Pid) || Pid <- Pids],
    % Sum up the results.
    WordCount = lists:sum(Results),
    io:format("We've got ~p words, Sir!~n", [WordCount]).

spawn_summarizer(S, Line) ->
    % Create a anonymous function and save it in the variable F.
    F = fun() ->
        % Split line into words.
        ListOfWords = string:tokens(Line, " "),
        Length = length(ListOfWords),
        io:format("process ~p calculated ~p words~n", [self(), Length]),
        % Send a tuple containing our pid and Length to S.
        S ! {self(), Length}
    end,
    % There is no return in erlang, instead the last value in a function is
    % returned implicitly.
    % Spawn the anonymous function and return the pid of the new process.
    spawn(F).

% The Variable Pid gets bound in the function head.
% In erlang, you can only assign to a variable once.
receive_result(Pid) ->
    receive
        % Pattern-matching: the block behind "->" will execute only if we receive
        % a tuple that matches the one below. The variable Pid is already bound,
        % so we are waiting here for the answer of a specific process.
        % N is unbound so we accept any value.
        {Pid, N} ->
            io:format("Received \"~p\" from process ~p~n", [N, Pid]),
            N
    end.

これをシェルで実行すると、次のようになります。

Eshell V5.6.5  (abort with ^G)
1> Lines = ["This is a string of text", "and this is another", "and yet another", "it's getting boring now"].
["This is a string of text","and this is another",
 "and yet another","it's getting boring now"]
2> c(countwords).
{ok,countwords}
3> countwords:count_words_in_lines(Lines).
process <0.39.0> calculated 6 words
process <0.40.0> calculated 4 words
process <0.41.0> calculated 3 words
process <0.42.0> calculated 4 words
Received "6" from process <0.39.0>
Received "4" from process <0.40.0>
Received "3" from process <0.41.0>
Received "4" from process <0.42.0>
We've got 17 words, Sir!
ok
4> 
于 2009-02-05T06:25:22.390 に答える
13

Erlang のスケーリングを可能にする重要な要素は、同時実行性に関連しています。

オペレーティング システムは、次の 2 つのメカニズムによって同時実行性を提供します。

  • オペレーティング システムのプロセス
  • オペレーティング システムのスレッド

プロセスは状態を共有しません。設計上、あるプロセスが別のプロセスをクラッシュさせることはありません。

スレッドは状態を共有します – 設計上、あるスレッドが別のスレッドをクラッシュさせる可能性があります – それがあなたの問題です。

Erlang では、1 つのオペレーティング システム プロセスが仮想マシンによって使用され、VM は、オペレーティング システム スレッドを使用するのではなく、Erlang プロセスを提供することによって、Erlang プログラムに並行性を提供します。つまり、Erlang は独自のタイムスライサーを実装します。

これらの Erlang プロセスは、メッセージを送信することによって相互に対話します (オペレーティング システムではなく、Erlang VM によって処理されます)。Erlang プロセスは、次の 3 つの部分からなるアドレスを持つプロセス ID (PID) を使用して相互にアドレス指定します<<N3.N2.N1>>

  • プロセス番号 N1 オン
  • VM N2 オン
  • 物理マシン N3

同じ VM 上の 2 つのプロセス、同じマシン上の異なる VM 上の 2 つのプロセス、または 2 つのマシンが同じ方法で通信します。したがって、スケーリングは、アプリケーションをデプロイする物理マシンの数とは無関係です (最初の概算で)。

Erlang は些細な意味でのみスレッドセーフです – スレッドはありません。(言語、つまり、SMP/マルチコア VM は、コアごとに 1 つのオペレーティング システム スレッドを使用します)。

于 2009-01-24T14:24:15.800 に答える
7

Erlang の仕組みを誤解しているかもしれません。Erlang ランタイムは、CPU でのコンテキスト切り替えを最小限に抑えますが、使用可能な CPU が複数ある場合は、すべてがメッセージの処理に使用されます。他の言語のような「スレッド」はありませんが、多数のメッセージを同時に処理できます。

于 2009-01-23T21:12:58.627 に答える
4

Erlang メッセージは純粋に非同期です。メッセージへの同期応答が必要な場合は、明示的にコーディングする必要があります。おそらく言われたことは、プロセスメッセージボックス内のメッセージは順番に処理されるということでした。プロセスに送信されたメッセージはすべてそのプロセス メッセージ ボックスに置かれ、プロセスはそのボックスから 1 つのメッセージを選択して処理し、適切と思われる順序で次のメッセージに進みます。これは非常にシーケンシャルな行為であり、受信ブロックはまさに​​それを行います。

クリスが言ったように、同期と順次を混同しているようです。

于 2009-02-10T17:29:41.843 に答える
3

参照透過性: http: //en.wikipedia.org/wiki/Referential_transparency_(computer_science)を参照してください

于 2009-01-23T21:07:51.507 に答える
-2

純粋な関数型言語では、評価の順序は重要ではありません。関数アプリケーション fn(arg1, .. argn) では、n 個の引数を並列に評価できます。これにより、高レベルの (自動) 並列処理が保証されます。

Erlang は、プロセスが同じ仮想マシンで実行できるか、別のプロセッサで実行できるかを判断する方法がないプロセス モデルを使用します。これが可能なのは、メッセージがプロセス間でコピーされ、共有 (可変) 状態がないためです。スレッドは共有メモリに依存するため、マルチプロセッサの並列処理はマルチスレッドよりもはるかに優れています。これは、8 コア CPU で並列に実行できるスレッドは 8 つだけですが、マルチプロセッシングは数千の並列プロセスに拡張できます。

于 2009-01-23T21:20:11.110 に答える