Erlang风格的并行(Erlang Style Concurrency)

简介:

我们常常能在 Erlang 的文档和源码之中看到Ulf Wiger这个名字,他是 Erlang 最初的开发者之一。最近他写了一篇博客《What is Erlang-Style Concurrency?》对于“Erlang风格的并行”发表了自己的看法,粗浅译来,给大家共享。

原文:http://ulf.wiger.net/weblog/?p=10
译文:http://erlang-china.org/news/erlang-style_concurrency.html
译者:jackyz

现下时不时都能看到“用 language X 来做 Erlang 风格的并行”这样的博客文章。我想这是好事,这表明大家都开始认真研究有关并行的问题了。

然而,对于“Erlang 风格的并行”这一术语,仍然缺乏一个权威的定义。我在这里尝试给出自己的定义。

Joe Armstrong在他的《Programming Erlang》中写了这么一段话:

“在Erlang中:
      进程的创建和销毁非常迅速
      进程之间传递消息非常迅速
      进程的行为独立于操作系统
      进程可以有大规模使用
      进程之间彻底独立不共享内存
      进程之间交互的唯一途径就是消息传递”

可以将这看作是一个“权威定义”,但稍嫌不够完整。

我想再加上这么几条:

  • 消息传递是异步的
  • 进程可以彼此监控
  • 可以选择性的接收消息
  • 远程进程和本地进程基本保持一致

上面的这些条目概略的描述了 Erlang 中并行的工作方式。那么,对于“Erlang 风格的并行”,是否这些条目全都是不可或缺呢?也许并非如此。

我想下面的这些特性都是必须的:

  • 迅速的进程创建和销毁
  • 不费力的支持至少10K以上的并行进程
  • 迅速的异步消息传递
  • 复制的消息传递机制(无共享的并行)
  • 进程监控
  • 选择性的消息接收机制

稍作解释:

速度和伸缩性

要让并行成为一种具有实用价值的基本建模手段,必须要让程序员能放心的创建解决问题所需的大量进程,而无需担心会因此而影响效率。若要以一句话来概况“Erlang 风格并行”的精髓,那就是——它让你可以按照问题自身内在的并行模式来构建应用。如果认为创建进程代价高昂,程序员就会尽量重用已有的进程;如果认为消息传递代价高昂,就会发明出其它的技术以避免传递消息。而这些手段通常是有害的(对于并行来说)。

凡事都有自己的局限。Erlang 被设计用来于做“代理风格”的并行,而不是大数据量的并行。在 Erlang 中你“只能”有约 120M 个并行进程,如果内存足够的话。我本人试过,在 20M 个并发进程时,Erlang 仍然保持一贯的性能(我当时没有更多的内存可以继续尝试)。对于某些极端应用来说,(约5-10微秒)创建一个进程的代价仍嫌昂贵,但相比UNIX进程或 POSIX线程,至少“廉价”了几个数量级。“Erlang 风格的并行”在进程的粒度上,应与 Erlang 大体保持一致。

异步消息传递

对于异步或者同步的消息传递,孰优孰劣曾经有过争论。的确,同步消息传递更易于理解。但在分布式的环境下,异步通讯(也就是所谓的“发送-祈祷”)则更符合直觉。基于同步消息传递的系统,在分布式的环境下也必须诉诸于某种形式的异步通讯机制,方可完成任务。尽管仍然存在争议,但无论如何异步消息传递仍是 “Erlang 风格并行”的一个特征。

复制机制

注意,这里的并不意味着说在所有的情况下都必须要完全复制所有的消息,重点不在方式,而在效果。这很重要,

主要原因如下:

  • 从可靠性角度考虑,进程不能共享内存
  • 在分布的环境下,复制不可避免,我们尽可能的在本地和远程的消息传递中都保持相同的机制

要求进程之间不能共享任何东西,这可能太过严格,很有可能会将大部分的编程语言都被排除在“Erlang 风格的并行”之外(甚至连Scala这样的语言都预留了在进程之间共享数据的可能)。那么好吧,我们至少应该说,Erlang 风格使得用非共享的方式来实现并行要比共享方式更为容易。

进程监控

这是“边界外”错误处理机制的根基。在 Erlang 中,典型的错误处理哲学是——让它崩溃。其要义就在于,你的程序只处理正常情况,出现异常的情况则交由监控进程来负责处理。这么做的结果是,在大型系统中,能漂亮的实现容错。

选择性的消息接收

有N中方法可以实现选择性的消息接收,但对于复杂的多路并行而言,你必须至少要支持其中的一种。在达致并发的路上,这一点很容易被忽视。简单的程序之中,你也不会很明显的感到它的必要,但若当你真正发现它的价值,你很可能已经被“复杂性爆炸”折磨很久了。

我建议将选择性的消息接收机制列为 Erlang 风格并发的一个充分条件。它可以通过对单一消息队列进行匹配的方式(Erlang, OSE Delta)来实现,也可以通过多邮箱/频道的方式(Haskell, .Net, UNIX 套接字)来实现。选一个,找个合适的例子,演练它,体验它。如果你愿意,可以参照我的“Structured Network Programming”演示。

—朴素的分隔线—

注:跟随这个链接你会发现“Erlang-Style Concurrency”正在迅速变成一个“即将臭大街”的名词,也许正是担心这种状况的出现,Ulf Wiger 发表了这篇博客,对于一些声称自己是 Erlang-Style 的 tricky ,划出了一道明确的标准,以正视听。只是后续的发展到底如何,还需继续观察。

译后 blah :

记得曾在 maillist 见到 Joe Armstrong 说过“Erlang(语言) 的力量不在语言,Erlang 是很浅显的语言,而是在于语言和虚拟机之间的高度适配”。spawn一个进程,选择性的从队列中取出一条消息,或者以copy的方式发送一个异步消息,这些都不难做到,基本上哪个语言都能做到;难的部分就在于将这些部分在虚拟机中不断打磨,以“极廉价的操作”为目标来高度优化,让其走出珠宝店,进入菜市场,成为编写程序时的一个可选常规工具;我们所使用的工具,它的特性,会反过来影响我们看待问题的方式。当它改变我们对系统建模的思维方式,并形成自己的一套思维模式时,即由此延生出了所谓的 COP 思想。这就是 Erlang 或者说 COP 所走过的路。类似 Scala 或其它以并发为设计目的之一的语言,若想达致 COP 的实用境界,很可能也必须要历经这个痛苦锻造过程的各个阶段。而对于 COP 思想来说,也将会增加一个新的具有实用价值的实践平台。

目录
相关文章
|
缓存 安全 Java
【Java并发编程实战14】构建自定义同步工具(Building-Custom-Synchronizers)(上)
JDK包含许多存在状态依赖的类,例如FutureTask、Semaphore和BlockingQueue,他们的一些操作都有前提条件,例如非空、任务已完成等。
128 0
|
缓存 Java API
【Java并发编程实战14】构建自定义同步工具(Building-Custom-Synchronizers)(中)
JDK包含许多存在状态依赖的类,例如FutureTask、Semaphore和BlockingQueue,他们的一些操作都有前提条件,例如非空、任务已完成等。
116 0
|
设计模式 缓存 算法
【Java并发编程实战14】构建自定义同步工具(Building-Custom-Synchronizers)(下)
JDK包含许多存在状态依赖的类,例如FutureTask、Semaphore和BlockingQueue,他们的一些操作都有前提条件,例如非空、任务已完成等。
169 0
|
开发工具 git
|
缓存 Java API
Java并发编程实战系列14之构建自定义的同步工具 (Building Custom Synchronizers)
类库中包含了许多存在状态依赖的类,例如FutureTask、Semaphore和BlockingQueue,他们的一些操作都有前提条件,例如非空,或者任务已完成等。
1348 0
[Erlang 0127] Term sharing in Erlang/OTP 上篇
之前,在 [Erlang 0126] 我们读过的Erlang论文 提到过下面这篇论文:  On Preserving Term Sharing in the Erlang Virtual Machine地址: http://user.
1324 0
|
C#
[Erlang 0112] Elixir Protocols
Why Elixir        为什么要学习Elixir?答案很简单,为了更好的学习Erlang.这么无厘头的理由?      Erlang语法设计几乎没有考虑过取悦开发者,所以学习之初的门槛略高.
1217 0