我一直在关注函数式编程语言和功能的日益增长的可见性.我调查了他们,没有看到上诉的原因.
然后,最近我参加了Kevin Smith在Codemash上的"Erlang基础知识"演讲.
我很喜欢这个演示文稿,并了解到函数式编程的许多属性使得更容易避免线程/并发问题.我理解缺少状态和可变性使得多个线程无法改变相同的数据,但Kevin说(如果我理解正确的话)所有通信都是通过消息进行的,并且同步处理消息(再次避免并发问题).
但我已经读到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建模此行为.
例如,您可以生成一个计算一行中单词数的过程.由于我们有几行,我们为每一行产生一个这样的过程并接收答案以从中计算总和.
这样,我们产生了进行"繁重"计算的进程(如果可用,则使用额外的核心),然后我们收集结果.
-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.
当我们在shell中运行它时,这就是它的样子:
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>
使Erlang扩展的关键是与并发性有关.
操作系统通过两种机制提供并发:
操作系统进程
操作系统线程
进程不共享状态 - 一个进程不能通过设计崩溃另一个进程.
线程共享状态 - 一个线程可以通过设计崩溃另一个 - 这是你的问题.
使用Erlang - 虚拟机使用一个操作系统进程,VM不向Erlang程序提供并发,而不是使用操作系统线程,而是提供Erlang进程 - 即Erlang实现自己的时间延迟.
这些Erlang进程通过发送消息(由Erlang VM而不是操作系统处理)相互通信.Erlang进程使用进程ID(PID)相互寻址,进程ID具有三部分地址<
:
处理没有N1
VM N2开启
物理机N3
同一个VM上的两个进程,在同一台机器上的不同VM或两台机器上以相同的方式进行通信 - 因此,您的扩展与您部署应用程序的物理机器数量无关(在第一个近似值中).
Erlang只是一个微不足道的线程安全 - 它没有线程.(语言即SMP /多核VM每个核心使用一个操作系统线程).
您可能对Erlang如何工作有误解.Erlang运行时最小化CPU上下文切换,但如果有多个CPU可用,则所有CPU都用于处理消息.您在其他语言中没有"线程",但您可以同时处理大量消息.