如何在C++中实现类似Erlang的发送和接收机制?

10

实际上,这个问题似乎有两个部分:

  • 如何实现模式匹配?
  • 如何实现发送和接收(即Actor模型)?

关于模式匹配部分,我一直在研究各种项目,比如AppProp。它们看起来很不错,但我无法让它们在最新版本(4.x)的g++上工作。另外,Felix语言也支持模式匹配,但并非真正的C++。

至于Actor模型,已经存在像ACT++和Theron这样的实现,但我找不到除了论文以外的任何东西.

个人而言,我使用线程和线程安全的消息队列来实现演员。消息是类似哈希结构的数据,并将其与一些预处理宏一起使用,实现了简单的模式匹配。

现在,我可以使用以下代码发送消息:

(new Message(this))
    ->set("foo", "bar")
    ->set("baz", 123)
    ->send(recipient);

以下是简单模式匹配的实现(qDebugqPrintable 是特定于 Qt 的):

receive_and_match(m)
    match_key("foo")    { qDebug("foo: %s", qPrintable(m->value("foo").toString())); }
    or_match_key("baz") { qDebug("baz: %d", m->value("baz").toInt()); }
    or_match_ignore
end_receive

不过,我认为这样看起来有点像hackish,而且不是很健壮。

你会怎么做呢?我有没有错过任何现有的工作?

7个回答

10
关于Actor模型,有一些现有的实现,比如ACT++和Theron,但我发现前者只有论文而没有实际应用,后者也只能单线程运行。
作为Theron的作者,我很好奇你为什么认为它只能单线程运行?
个人而言,我使用线程和线程安全的消息队列来实现了actors。
这就是Theron的实现方式... :-)
Ash

说实话,我不知道为什么我认为Theron是单线程的。重新阅读网站并没有给我这种印象。对于造成的困惑,我深表歉意,并且我认为如果我再试一次Theron也是公平的! - user4210
在 Matlab 的 mex 文件中使用 Theron 进行 Actors 并发编程,我有一个愉快的体验。 - Chinasaur

4
erlang的重要之处在于如何利用其特性来构建健壮的系统。发送/接收模型是不共享的,而且需要显式复制。进程本身是轻量级线程。如果你想要erlang模型的健壮性特性,则最好使用真正的进程和IPC而不是线程。如果您想要强大的消息传递,可能需要对内容进行序列化和反序列化。尤其是要考虑类型安全。在C++中进行模式匹配并不总是美观的,但是会有一个很好的模式 - 您最终将创建一个使用某种形式的多态性来实现目标的调度程序对象。虽然如果不小心会出现xml over pipes的情况:)。实际上,如果您想要erlang模型,那么您真的应该使用erlang。如果存在缓慢的部分,我相信您可以使用外部函数接口来增强程序。重新实现部分的问题在于您无法获得良好的协同库和解决方案。您已有的解决方案看起来与C++并不相似。

3

我正在为C++实现一个名为“acedia”的演员库(谷歌上尚无相关信息),它使用“类型匹配”。该库是我的硕士论文项目,您可以使用它向演员发送任何类型的数据。

以下是一小段示例:

recipient.send(23, 12.23f);

在接收方面,您可以像这样分析接收到的消息:

Message msg = receive();
if (msg.match<int, float>() { ... }

或者您可以定义一个规则集,以便为您调用函数或方法:

void doSomething(int, float);

InvokeRuleSet irs;
irs.add(on<int, float>() >> doSomething);
receiveAndInvoke(irs);

匹配类型和值是可能的:

Message msg = receive();
if (msg.match<int, float>(42, WILDCARD) { ... }
else if (msg.match<int, float>() { ... }

常量“WILDCARD”表示任何值都将被接受。不传递参数等同于将所有参数设置为“WILDCARD”; 这意味着您只想匹配类型。
这确实是一个小片段。您也可以像在Scala中那样使用“case classes”。它们类似于erlang中的“原子”。以下是更详细的示例:
ACEDIA_DECLARE_CASE_CLASS(ShutdownMessage)
ACEDIA_DECLARE_CASE_CLASS(Event1)
ACEDIA_DECLARE_CASE_CLASS(Event2)

为了响应定义的案例类,您可以编写一个如下的Actor:
```scala class MyActor extends Actor { def receive = { case CaseClass1 => // do something case CaseClass2 => // do something else case _ => // handle the default case } } ``` 注意,这里的案例类是指Scala中的case class。
class SomeActor : public Actor
{

  void shutdown() { done = true; }
  void handleEvent1();
  void handleEvent1();

  public:

    SomeActor() : done(false) { }

    virtual void act()
    {
      InvokeRuleSet irs;
      irs
        .add(on<ShutdownMessage>() >> method(&SomeActor::shutdown))
        .add(on<Event1>() >> method(&SomeActor::handleEvent1))
        .add(on<Event2>() >> method(&SomeActor::handleEvent2))
      ;
      while (!done) receiveAndInvoke(irs);
    }

};

创建一个新的Actor并启动它,您只需要编写以下内容:
Acedia::spawn<SomeActor>();

尽管该库甚至还未达到测试版阶段,但所展示的片段是有效的,我已经在其上运行了一个应用程序。该库的一个主要目标是支持分布式编程(也跨越网络)。
你的问题有一段时间了,但如果你对它感兴趣,请告诉我! :)

我肯定很感兴趣;它看起来非常不错。是否有一个网站可以下载这个库/查看文档?您会开源吗? - user4210

2

您可以使用Qt的信号/槽机制来模仿这种行为,尤其是因为Qt的信号/槽支持多线程。


0

如果你想在C++中拥有像Erlang一样强大的演员和模式匹配,也许Rust就是答案。

当然,在五年前OP提问时,这还没有公开出现,截至2014年4月,它仍未达到v1.0版本,但它的进展非常顺利,而且肯定正在稳定下来,足够多的语言核心已经稳定了。

好吧,它不是C++,但它具有与C++相同的内存管理方法,除了默认支持无共享内存的轻量级任务(然后提供控制库功能以进行共享-"Arc"); 它可以直接调用(并直接公开)'extern C'函数。你不能与C++共享模板库头文件,但你可以编写类似于C++集合类的泛型(反之亦然),以传递对数据结构的引用。


0

我肯定会有兴趣查看你的“acedia”库,并乐意以任何方式帮助。Erlang拥有一些精妙的结构,C++肯定可以从这样的库中受益。


0

我已经下载了它,一有机会就会试用它。 - user4210
1
更新:acedia已经过时,请检查libcppa替代品:https://sourceforge.net/projects/acedia/ - neverlord

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接