功能语言(特别是Erlang)如何/为什么能很好地扩展?

我一直在观察函数式编程语言和功能的日益增长的可见性。 我看着他们,没有看到上诉的理由。

然后,最近我在Codemash参加了Kevin Smith的“Erlang基础”演示。

我很享受演示文稿,并了解到函数式编程的许多属性使得避免线程/并发问题变得更容易。 我知道缺少状态和可变性使得多线程无法改变相同的数据,但凯文说(如果我理解正确的话)所有的通信都是通过消息进行的,并且消息同步处理(再次避免了并发问题)。

但是我已经读过Erlang用于高度可扩展的应用程序(爱立信首先创建它的全部原因)。 如果一切都作为同步处理的消息处理,那么如何有效地处理每秒数千个请求? 这不就是为什么我们开始转向异步处理 - 所以我们可以利用同时运行多个线程的操作并实现可扩展性? 看起来这种架构虽然更安全,但在可伸缩性方面却倒退了一步。 我错过了什么?

我了解Erlang的创建者故意避免支持线程以避免并发问题,但我认为多线程对于实现可伸缩性是必要的。

函数式编程语言如何固有地线程安全,但仍然可以扩展?


功能语言不(通常)依赖于变量变量。 因此,我们不必保护变量的“共享状态”,因为该值是固定的。 这反过来又避免了传统语言在整个处理器或机器上实现算法所必须经历的大部分环箍跳跃。

Erlang通过在消息传递系统中进行烘焙,使其能够在一个基于事件的系统上运行,其中一段代码只是担心接收消息和发送消息,而不是担心更大的图像,所以它比传统的功能语言更进一步。

这意味着程序员(名义上)不关心消息是在另一个处理器或机器上处理的:简单地发送消息足以让它继续下去。 如果它关心响应,它会等待它作为另一个消息。

最终的结果是每个片段都独立于其他片段。 没有共享代码,没有共享状态和来自消息系统的可以分布在许多硬件(或不是)中的所有交互。

将其与传统系统进行对比:我们必须将互斥体和信号量置于“受保护”变量和代码执行的周围。 我们在通过堆栈进行函数调用时有严格的绑定(等待返回发生)。 所有这些都造成了像Erlang这样的无共享系统的瓶颈问题。

编辑:我也应该指出,Erlang是异步的。 你发送你的消息,也许/有一天另一条消息回来。 或不。

Spencer关于无序执行的观点也很重要,并得到了很好的回答。


消息队列系统很酷,因为它有效地产生了“正在等待结果”的效果,这是您正在阅读的同步部分。 令人难以置信的是,这意味着行不需要按顺序执行。 考虑下面的代码:

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

考虑一下,methodWithALotOfDiskProcessing()需要大约2秒才能完成,而methodWithALotOfNetworkProcessing()需要大约1秒才能完成。 在过程语言中,这段代码需要大约3秒钟的时间才能运行,因为这些代码将按顺序执行。 我们正在浪费时间等待一种可以与另一种方法同时运行的方法,而不需要争夺单个资源。 在功能语言中,代码行并不指示处理器何时尝试它们。 一种功能性语言会尝试如下所示:

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.

多么酷啊? 通过继续使用代码,只需等待,我们已经将等待时间自动减少到两秒钟! :D所以是的,虽然代码是同步的,但它往往与过程语言有不同的含义。

编辑:

一旦你把这个概念与Godeke的文章结合起来,很容易想象它能够利用多个处理器,服务器场,冗余数据存储以及谁知道其他内容。


这很可能是你与顺序混合同步

erlang中函数的主体正在被顺序处理。 所以斯宾塞对这种“自动效应”所说的话并不适用于erlang。 尽管你可以用erlang来建模这个行为。

例如,你可以产生一个计算一行中单词数的过程。 当我们有几行时,我们为每一行生成一个这样的过程,并接收从中计算出总和的答案。

这样,我们产生了进行“重”计算的进程(利用额外的内核,如果可用的话),然后我们收集结果。

-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> 
链接地址: http://www.djcxy.com/p/47733.html

上一篇: How/why do functional languages (specifically Erlang) scale well?

下一篇: How are functors in Haskell and OCaml similar?