c++ rust维基百科 - 演员模型:为什么是erlang特别的? 或者,为什么你需要另一种语言呢?





model介绍 actor (6)


有C ++的实际演员库:

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

我一直在研究学习erlang,结果,一直在阅读(好,撇取)演员模型。

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

这在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的一个实例。 参与者之间只能通过消息传递进行通信。 (即推)。 行动者注册自己的初始化中央地图,允许其他行为者找到它们,并允许中央功能贯穿它们。

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

还有什么我想念的,或者是模型真的很明显?

我绝对没有试图在这里开始一场火焰战争,我只是想了解我错过了什么,因为这基本上是我已经做的能够有点理由关于并发代码。




Casablanca是另一个演员模特儿新人。 典型的异步接受如下所示:

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");
}

(就我个人而言,我发现github.com/actor-framework/actor-framework在隐藏匹配好的界面的模式方面做得更好。)




这实际上是一个很好的问题,并且已经得到了很好的答案,可能还没有说服力。

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

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

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

有一个局部的解决方法:使用租约而不是锁,但是你不能保证你使用的所有库也能做到这一点。 关于正确性的逻辑和推理很快就会变得非常有趣。 此外,租约缓慢恢复(超时到期后),所以整个系统在发生故障时变得非常缓慢。

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

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




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

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

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

问题不在于执行演员,这并不困难。 问题是让所有的东西一起工作:进程,通信,垃圾收集,语言原语,错误处理等等。例如,使用OS线程缩小比例,所以你需要自己做。 这就像试图“销售”一种面向对象语言,你只能拥有1k个对象,而且它们很难创建和使用。 从我们的角度来看,并发是构建应用程序的基本抽象。

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




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

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

例如,它强制两个进程不能共享状态以保护您免受副作用。 这并不意味着每个函数都必须是“纯”的,即所有内容都是透明的(显然不是这样,尽管尽可能多地将程序透明地透明化,实际上是大多数Erlang项目的明确设计目标),而是两过程不会不断创造与共享状态或争用相关的竞争条件。 (顺便说一句,这在Erlang的背景下更多的是“副作用”的含义;知道这可以帮助你解释一些讨论,比如当与哈斯克尔相比或者玩“纯”语言时,Erlang是否“真正有效或者没有” 。)

另一方面,Erlang运行时确保消息的传递。 在这种环境中,您必须完全通过非托管端口,管道,共享内存和公共文件进行通信,而OS内核是唯一管理的内核,并且这些资源的操作系统内核管理与Erlang相比极其微乎其微运行时提供)。 这并不意味着Erlang保证RPC(无论如何,消息传递不是 RPC,也不是方法调用!),它不保证你的消息被正确地处理,并且它不保证你的过程试图发送消息存在或者还活着。 它只是保证交付,如果你发送的东西在那一刻恰好是有效的。

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

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

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




那么,它可以。 它只是不会隐式创建。 如果我不得不猜测,这可能与Java对象总是被堆分配的事实有关。

在C ++中,默认的拷贝构造函数是一个成员级的浅拷贝。 如果一个类拥有在堆上分配的内存(通过一个原始指针),这将导致该副本与原始内容共享内存,这不是你想要的。

想象一下,Java有这种行为。 任何具有作为对象的字段的类(基本上都是这些对象)将具有错误的行为,并且您需要自己重写它。 对于99%的病例,您没有任何麻烦。 此外,你刚刚为自己创建了一个微妙的陷阱 - 想象你不小心忘记覆盖默认的拷贝构造函数。 如果它是默认生成的,并且您尝试使用它,则编译器根本不会发出抱怨,但是您的程序在运行时会出现错误。

即使他们创建了一个执行深层复制的默认复制构造函数,但我不确定这会特别有用。 无论如何,您不仅倾向于在Java中执行比C ++更少的副本,但您并不总是希望深度复制字段。

你拥有的对象,以及你持有的对象,因为你需要它们,但是不负责任,只是字段。 所有权和借款不是一流的概念。 对于你拥有的对象,你想要深拷贝它们(除非它们是不可变的,在这种情况下,你不应该打扰),而对于你只需要引用的对象,你想复制引用。

我会争辩说,一个只是无意识地复制一切的拷贝构造函数也不适合于许多类。 尽管如此,默认情况下不止是浅层复制。





c++ concurrency erlang actor message-passing