演员模型:为什么是erlang特别的? 或者,为什么你需要另一种语言呢?

我一直在研究学习erlang,因此,一直在阅读(好吧,略读)演员模型。

根据我的理解,actor模型只是一组函数(在erlang中称为“processes”的轻量级线程中运行),它们之间只能通过消息传递进行通信。

在C ++或其他语言中,这似乎相当简单:

class BaseActor { std::queue<BaseMessage*> messages; CriticalSection messagecs; BaseMessage* Pop(); public: void Push(BaseMessage* message) { auto scopedlock = messagecs.AquireScopedLock(); messagecs.push(message); } virtual void ActorFn() = 0; virtual ~BaseActor() {} = 0; } 

每个进程都是派生的BaseActor的一个实例。 参与者之间只能通过消息传递进行交stream。 (即推)。 行动者注册自己的初始化中央地图,允许其他行为者find他们,并允许中央function贯穿其中。

现在,我明白我错过了,或者更确切地说,在这里重要的一个问题,即:缺乏屈服意味着单个演员可以不公平地消耗过多的时间。 但是跨平台的协程是使C ++变得困难的主要原因吗? (例如Windows有光纤。)

还有什么我失踪,或者是模型真的这是显而易见的?

我绝对不是试图在这里开始一场激烈的战争,我只是想明白我错过了什么,因为这基本上是我已经做了一些有关并发代码的原因。

C ++代码并不涉及公平性,隔离性,错误检测或分发这些都是Erlang作为其演员模型一部分带来的东西。

  • 不允许任何演员挨饿(公平)
  • 如果一个演员崩溃,它应该只影响该演员(隔离)
  • 如果一个演员崩溃,其他演员应该能够检测到这个崩溃并作出反应(故障检测)
  • 参与者应该能够通过networking进行通信,就好像他们在同一台机器上(分发)

此外,束SMP模拟器带给参与者的JIT调度,将他们移动到当前使用率最低的核心,并且如果不再需要某些核心上的线程,则将其hibernate。

另外,用Erlang编写的所有库和工具都可以假定这是世界的工作方式和相应的devise。

这些东西在C ++中并不是不可能的,但是如果添加Erlang在几乎所有主要的hw和osconfiguration上的工作,它们会变得越来越困难。

编辑:刚刚findUlf Wiger关于他看到的erlang风格并发性的描述。

我不喜欢引用自己,而是从维尔丁的第一编程规则

在另一种语言中任何足够复杂的并发程序都包含一个特殊的非正式指定的错误的Erlang的一半缓慢的实现。

关于格林普斯。 乔(阿姆斯特朗)也有类似的规则。

问题不在于执行演员,这并不困难。 问题是让所有的东西一起工作:进程,通信,垃圾收集,语言原语,error handling等…例如使用操作系统线程缩放严重,所以你需要自己动手。 这就像试图“销售”OO语言,你只能拥有1k个对象,而且它们很难创build和使用。 从我们的angular度来看,并发是构build应用程序的基本抽象。

越来越多,所以我会停下来。

这实际上是一个很好的问题,并且得到了很好的答案,可能还不能令人信服。

为了给这里已经很好的答案添加阴影和重点,请考虑一下Erlang 带走了什么(与传统的通用语言如C / C ++相比),以实现容错和正常运行时间。

首先,它带走了锁。 乔·阿姆斯特朗的书列出了这个想法的实验:假设你的进程获得一个锁,然后立即崩溃(内存故障导致进程崩溃,或权力失败的部分系统)。 下一次进程等待同一个锁,系统刚刚死锁。 这可能是一个明显的锁,就像示例代码中的AquireScopedLock()调用一样; 或者它可能是一个由内存pipe理器代表的隐式锁,例如在调用malloc()或free()时。

无论如何,你的进程崩溃现在已经停止了整个系统的进展。 菲尼。 故事结局。 你的系统已经死了 除非你可以保证你在C / C ++中使用的每个库都不会调用malloc,也永远不会获得一个锁,否则你的系统不是容错的。 Erlang系统可以在负载过重的情况下随意杀死进程,以便进展,因此在规模上,您的Erlang进程必须能够在任何单一的执行点上进行攻击以保持吞吐量。

有一个部分的解决方法:到处使用租约而不是锁,但是你不能保证所有你使用的库也是这样做的。 关于正确性的逻辑和推理很快就会变得多毛。 此外,租约恢复缓慢(超时过期后),所以整个系统在面对失败时真的很慢。

其次,Erlang带走了静态types,这反过来又使热代码交换和同时运行相同代码的两个版本。 这意味着您可以在运行时升级您的代码而不停止系统。 这就是系统如何保持9个或32毫秒的停机时间/年。 他们只是升级到位。 您的C ++函数必须手动重新链接才能升级,并且不支持同时运行两个版本。 代码升级需要系统停机,如果你有一个大型集群不能同时运行多个版本的代码,那么你需要一次完成整个集群。 哎哟。 而在电信世界,这是不可容忍的。

另外Erlang带走共享内存和共享共享垃圾回收; 每个轻量级进程都是独立垃圾收集。 这是第一点的简单扩展,但是强调为了实现真正的容错性,需要不依赖于互锁的进程。 这意味着对于大型系统来说,与Java相比,GC暂停是可以容忍的(小而不是暂停半小时,以完成8GB GC)。

有C ++实际的演员库:

还有一些其他语言库的列表 。

关于actor模型的内容要less得多,关于在C ++中正确地写OTP类似的东西有多困难。 另外,不同的操作系统提供了截然不同的debugging和系统工具,而Erlang的虚拟机和几种语言结构支持统一的方法来确定所有这些过程是否很难以统一的方式完成(或者可能做到在所有)在几个平台。 (重要的是要记住,Erlang / OTP早于“演员模型”这个术语,所以在某些情况下,这种讨论是在比较苹果和翼手龙;伟大的想法很容易得到独立的发明。)

所有这一切意味着,虽然你当然可以用另一种语言编写一个“演员模型”程序套件(我知道,我已经在Python,C和Guile中做了很长一段时间,但在我遇到Erlang之前却没有意识到这一点,包括监视器和链接,在我听说“演员模型”这个词之前),理解你的代码实际产生的过程以及它们之间发生的事情是非常困难的。 Erlang强制规定操作系统在没有重大内核检修的情况下不能执行 – 内核检修可能不会有利于整体。 这些规则performance为对程序员的一般限制(如果你真的需要这个限制的话)以及系统为程序员保证的基本承诺(如果你真的需要也可以故意破坏)。

例如,它强制两个进程不能共享状态来保护您免受副作用。 这并不意味着每一个函数都必须是“纯粹”的,即所有东西都是透明的(显然不是这样,尽pipe尽可能多地使程序透明,实际上是大多数Erlang项目的一个明确的devise目标),而是两个过程不会不断创造与共享状态或争用相关的竞争条件。 (顺便说一下,在Erlang的背景下,这更多的是“副作用”的意思;知道这可以帮助你解释一些关于Erlang与Haskell相比是“真正的函数还是玩”“纯”语言。)

另一方面,Erlang运行时保证消息的传递。 在一个必须通过非托pipe端口,pipe道,共享内存和公共文件进行纯粹通信的环境中,这是一个非常严重的问题,而操作系统内核是唯一pipe理这些资源的操作系统(和内核pipe理相比,Erlang运行时提供)。 这并不意味着Erlang保证RPC(无论如何,消息传递不是 RPC,也不是方法调用!),它不保证你的消息是正确的,并且不保证你的过程试图发送消息存在或活着,要么。 它只是保证交付,如果你发送的东西在那一刻恰好是有效的。

build立在这个诺言是承诺监测和链接是准确的。 基于此,一旦你掌握了系统的运行情况(以及如何使用erl_connect …),Erlang运行时就会使“networking集群”的整个概念消失。 这允许你跳过一组棘手的并发案例,这使得在成功案例的编码方面有了一个很大的开端,而不是陷入裸体并发编程所需的防御技术的泥沼之中。

所以它不是真的需要 Erlang,它的语言,它的运行时间和OTP已经存在,以一种相当干净的方式expression,在另一种语言中实现接近它的任何东西都非常困难。 OTP只是一个难以遵循的行为。 同样,我们也不需要 C ++,我们可以坚持原始的二进制input,Brainfuck,并考虑汇编语言的高级语言。 我们也不需要火车或轮船,因为我们都知道如何行走和游泳。

所有这一切,虚拟机的字节码都有很好的文档logging,并且出现了一些可以编译或者使用Erlang运行时的替代语言。 如果我们把这个问题分解成一个语言/句法部分(“我必须了解Moon Runes做并发吗?”)和一个平台部分(“OTP是做成并发的最成熟的方法,它会引导我绕过最棘手的问题,在并发的分布式环境中发现的最常见的陷阱?“)那么答案是(”不“,”是“)。

卡萨布兰卡是另一个新的演员模型的孩子。 典型的asynchronous接受如下所示:

 PID replyTo; NameQuery request; accept_request().then([=](std::tuple<NameQuery,PID> request) { if (std::get<0>(request) == FirstName) std::get<1>(request).send("Niklas"); else std::get<1>(request).send("Gustafsson"); } 

(就我个人而言,我发现CAF在隐藏匹配好的界面的模式方面做得更好。)