9

现在有很多关于不使用锁和使用像 Erlang 这样的消息传递方法的讨论。或者关于使用不可变数据结构,例如函数式编程与 C++/Java。

但我关心的是以下几点:

  1. AFAIK,Erlang 不保证消息传递。消息可能会丢失。如果您不得不担心消息丢失,算法和代码是否会变得臃肿并再次变得复杂?无论您使用什么分布式算法,都不能依赖于有保证的消息传递。
  2. 如果 Message 是一个复杂的对象怎么办?复制和发送消息与将消息保存在共享位置(例如两个进程都可以访问的数据库)相比,是否存在巨大的性能损失?
  3. 你真的可以完全取消共享状态吗?我不这么认为。例如在数据库中,您必须访问和修改相同的记录。您不能在那里使用消息传递。您需要锁定或假设乐观并发控制机制,然后对错误进行回滚。Mnesia 是如何工作的?
  4. 此外,您并不总是需要担心并发性。任何项目也将有一大段代码,它们根本不需要对并发或事务做任何事情(但它们确实需要考虑性能和速度)。许多这些算法依赖于共享状态(这就是为什么传递引用或指针如此有用)。

鉴于这一事实,用 Erlang 等编写程序是一件痛苦的事情,因为您无法做任何这些事情。可能是,它使程序变得健壮,但对于解决线性规划问题或计算凸包等问题,性能更重要,当算法与并发/事务无关时,强制不变性等是一个糟糕的决定. 不是吗?

4

7 回答 7

7
  1. 这就是现实生活:无论语言/平台如何,您都需要考虑这种可能性。在分布式世界(现实世界)中,事情失败了:忍受它。

  2. 当然是有代价的:在我们的宇宙中没有什么是免费的。但是您不应该使用另一种介质(例如文件、数据库)而不是在通信管道中穿梭“大对象”吗?您始终可以使用“消息”来指代存储在某处的“大对象”。

  3. 当然不是:函数式编程/Erlang OTP 背后的想法是尽可能“隔离”被“共享状态”操纵的区域。此外,明确标记共享状态发生突变的位置有助于可测试性和可追溯性

  4. 我相信你没有抓住重点:没有灵丹妙药。如果您的应用程序无法使用 Erlang 成功构建,请不要这样做。您总是可以以另一种方式,即使用不同的语言/平台,整个系统的其他部分。Erlang 在这方面与其他语言没有什么不同:为正确的工作使用正确的工具

请记住:Erlang 旨在帮助解决并发异步分布式问题。例如,它没有针对在共享内存块上高效工作进行优化......除非您计算与nif在游戏的共享块上工作的函数的接口:-)

于 2009-11-27T19:33:27.977 回答
3

无论如何,现实世界的系统总是混合的:我不相信现代范式在实践中试图摆脱可变数据和共享状态。

然而,目标是不需要并发访问这个共享状态。程序可以分为并发和顺序,并发部分使用消息传递和新范式。

并非每个代码都会获得相同的投资:人们担心线程从根本上“被认为是有害的”。像 Apache 这样的东西可能需要传统的并发线程,而像这样的关键技术可能会在几年内经过精心改进,这样它就可以在完全并发的共享状态下爆发。操作系统内核是另一个例子,其中“无论多么昂贵都可以解决问题”可能是有意义的。

fast-but-broken 没有任何好处:但是对于新代码,或者没有得到太多关注的代码,它可能根本不是线程安全的,并且它不会处理真正的并发,所以相对的“效率”是无关紧要的。一种方法有效,一种方法无效。

不要忘记可测试性:此外,您可以对测试赋予什么价值?基于线程的共享内存并发根本无法测试。消息传递并发是。因此,现在您遇到了可以测试一种范式但不能测试另一种范式的情况。那么,知道代码已经过测试有什么价值呢?甚至不知道其他代码是否适用于所有情况的危险?

于 2009-11-27T19:32:26.203 回答
3

关于您对 Erlang 的误解的几点评论:

  • Erlang 保证消息不会丢失,并且它们会按照发送的顺序到达。 一个基本的错误情况是机器 A 无法与机器 B 对话。当发生这种情况时,进程监视器和链接将触发,系统节点关闭消息将被发送到为其注册的进程。什么都不会被默默地丢弃。进程将“崩溃”并且主管(如果有)试图重新启动它们。
  • 对象不能被改变,所以它们总是被复制。确保不变性的一种方法是将值复制到其他 erlang 进程的堆中。另一种方法是在共享堆中分配对象,对它们进行消息引用,并且根本没有任何改变它们的操作。Erlang 是性能第一!如果您需要停止所有进程以垃圾收集共享堆,则实时会受到影响。问Java。
  • Erlang 中有共享状态。Erlang 并不以此为荣,但它对此很务实。一个例子是本地进程注册表,它是一个将名称映射到进程的全局映射,以便可以重新启动系统进程并声明它们的旧名称。Erlang 只是尽可能地避免共享状态。公开的 ETS 表是另一个示例。
  • 是的,有时 Erlang 太慢了。这发生在所有语言中。有时Java太慢了。有时 C++ 太慢了。仅仅因为游戏中的一个紧密循环必须下降到汇编来启动一些严肃的基于 SIMD 的矢量数学,你不能推断一切都应该用汇编编写,因为它是唯一在重要时快速的语言。重要的是能够编写具有良好性能的系统,而 Erlang 管理得很好。查看 yaws 或 rabbitmq 的基准。

你的事实不是关于 Erlang 的事实。即使您认为 Erlang 编程很痛苦,您也会发现其他人也因此而开发了一些很棒的软件。你应该尝试在 Erlang 中编写一个 IRC 服务器,或者其他非常并发的东西。即使你再也不会使用 Erlang,你也会学会以另一种方式考虑并发性。当然,你会的,因为 Erlang 非常简单。

那些不懂 Erlang 的人,注定要重新实现它很糟糕。

好吧,原来是关于 Lisp 的,但是……它是真的!

于 2009-11-27T22:30:24.403 回答
3

您的问题中有一些隐含的假设 - 您假设所有数据都可以放在一台机器上,并且应用程序本质上本地化到一个地方。

如果应用程序太大而无法安装在一台机器上,会发生什么?如果应用程序超出一台机器会发生什么?

如果应用程序适合在一台机器上,您不希望有一种编程方式,而一旦超过一台机器,您不希望有一种完全不同的编程方式。

如果你想创建一个容错的应用程序会发生什么?要使某些东西具有容错性,您至少需要两台物理上分开的机器并且不共享。当您谈论共享和数据库时,您忽略了诸如 mySQL 集群之类的东西通过在物理上分离的机器中维护数据的同步副本来精确地实现容错 - 有很多您看不到的消息传递和复制表面 - Erlang 只是暴露了这一点。

您的编程方式不应突然改变以适应容错和可伸缩性。

Erlang 主要是为构建容错应用程序而设计的。

多核上的共享数据有它自己的一系列问题 - 当您访问共享数据时,您需要获取锁 - 如果您使用全局锁(最简单的方法),您最终可能会在访问共享时停止所有核心数据。由于缓存问题,多核上的共享数据访问可能会出现问题,如果内核具有本地数据缓存,则访问“远距离”数据(在某些其他处理器缓存中)可能非常昂贵。

许多问题本质上是分布的,而且数据永远不会同时在一个地方可用,所以——这类问题非常适合 Erlang 的思维方式。

在分布式设置中,“保证消息传递”是不可能的——目标机器可能已经崩溃。因此,Erlang 不能保证消息传递——它采用不同的方法——系统会告诉你它是否未能传递消息(但前提是你使用了链接机制)——然后你可以编写自己的自定义错误恢复。)

对于纯数字运算,Erlang 是不合适的——但在混合系统中,Erlang 擅长管理计算如何分配到可用处理器,因此我们看到很多系统中 Erlang 管理问题的分布和容错方面,但是问题本身是用不同的语言解决的。

和其他语言被使用

于 2009-11-30T14:55:30.487 回答
1

例如在数据库中,您必须访问和修改相同的记录

但这由数据库处理。作为数据库的用户,您只需执行查询,数据库确保它是独立执行的。

至于性能,消除共享状态的最重要的事情之一是它可以实现新的优化。共享状态并不是特别有效。你会得到核心争夺相同的缓存线,并且数据必须写入内存,否则它可能会保留在寄存器或 CPU 缓存中。

许多编译器优化也依赖于没有副作用和共享状态。

您可以说,保证这些事情的更严格的语言比 C 之类的语言需要更多的优化才能提高性能,但它也使编译器更容易实现这些优化。

许多类似于并发问题的问题出现在单线程代码中。现代 CPU 是流水线的,无序执行指令,每个周期可以运行 3-4 个指令。因此,即使在单线程程序中,编译器和 CPU 也必须能够确定哪些指令可以交错并并行执行。

于 2009-11-27T19:35:37.300 回答
0
  1. Erlang 为同步调用提供了监督者和 gen_server 回调,因此如果消息未传递,您将知道它:gen_server 调用返回超时,或者如果监督者被触发,您的整个节点将被关闭和启动。
  2. 通常如果进程在同一个节点上,消息传递语言会优化数据复制,所以它几乎就像共享内存,除非对象被两者使用后更改,这无论如何都不能使用共享内存来完成
  3. 有一些状态由进程通过在递归尾调用中传递给自己来保持,当然也可以通过消息传递一些状态。我很少使用 mnesia,但它是一个事务性数据库,所以一旦你将操作传递给 mnesia(并且它已经返回),你几乎可以保证它会通过..
  4. 这就是为什么使用端口或驱动程序很容易将此类应用程序绑定到 erlang 的原因。最简单的是端口,它很像一个 unix 管道,虽然我认为性能不是那么好......正如所说,消息传递通常最终只是指针传递,因为 VM/编译器优化了内存复制.
于 2009-11-30T02:28:58.220 回答
-1

为了正确起见,共享是要走的路,并尽可能保持数据标准化。为了即时性,发送消息以通知更改,但始终通过轮询来备份它们。消息被丢弃、重复、重新排序、延迟——不要依赖它们。

如果您担心的是速度,请先进行单线程处理,然后调出日光。然后,如果您有多个内核并且知道如何拆分工作,请使用并行性。

于 2009-11-27T19:46:06.440 回答