消息传递

消息传递

@(CAF 文档翻译)

CAF总是异步传递息,进一步说,CAF即不保证消息正确送达,也不保证在分布式环境中送达的顺序.CAF默认使用TCP协议, 但是可以不需要直接链接就可以从一个节点发送消息给另一个节点.在这种情况下,消息发送到中间节点,如果前端节点发送失败就会丢失.同样地转发路径能动态改变路径,因此引起到达顺序不一至.

CAF消息层有三个方式发送消息send, request, 和delegate, 前一个方式简单地将消息放入队列到接收者的邮槽.后两种方式在下面的小节中介绍.

邮槽数据结构

当消息被放入actor的邮槽队列中,CAF添加元数据和处理路径并包装消息的内容到mailbox_element(见下图)
UML class diagram for `mailbox_element

发送者作为strong_actor_ptr存储(见Pointer) 和表示消息的源.消息ID可以是0或者无效值或者是标识接收者返回数据给发送者唯一请求的正数.stagesvector保存消息的发送路径.回复消息,i.e.消息处理返回的值在调用stages.pop_back()后被发送给stages.back().这样允许CAF构建任意数量的路径, 如果没有更多的stage剩下,则回复到达发送者.最后content()通过type-erased tuple保存消息的内容.

CAF自动创建邮槽数据而且通常对于开发者来说是不可见的.不管怎么样知道消息在内部是如何被处理的帮助我们理解消息传递层的行为.

值得一提的是CAF通常包装邮槽数据和它的内容到单个对象为了减少内存分配.

写时复制

CAF 允许多个actors隐式的共享消息内容,只要没有actor修改消息内容.这样允许不需要复制消息内容,就可以发送到所有的订阅相关内容的actors的组.

每当其它actors持有消息的引用和如果超过一个消息处理程序获取一个可变的引用,则Actors拷贝消息内容.

消息类型的需求:

CAF中的消息类型必需满足下列需求:
1. 可序列化或者可视的(见 类型可视(序列化和字符转换))
2. 默认构造函数
3. 拷贝构造函数

如果提供serialize(Serializer&, T&) or serialize(Serializer&, T&, const unsigned int).自由函数,则类型是可序列化的.相应地, 如果提供一个inspect(Inspector&, T&)自由函数,那么类型就是可视的.

需求1是满足需求2的前提, 因为CAF需要能够在创建一个对象之前能够调用serializeorinspect时.第三个需求允许CAF实现写时复制(see 写时复制)

默认和系统处理程序.

CAF有三个系统级别的消息类型(down_msg, exit_msg, and error),所有的actor不管当前处于任何状态都应该需要处理.因此, 基于事件的actors有专门的消息处理函数中处理这些消息.此外, 基于事件的actors有个未被匹配消息的处理函数.注意:阻塞的actors没有那些专门的处理函数(见阻塞Actors)

向下处理函数

Actors可以通过调用self->monitor(other)监控其它actors的寿命.这会造成当其它的actors退出时,CAF的运行系统会发送一个down_msg.除非通过set_down_handler(f)提供一个自定义的处理函数,否则Actors会忽略向下消息.f是一个签名类型为void (down_message) 或者 void (scheduled_actor*, down_message&)的函数对象.后一个函数签名允许用户使用一个自由函数实现一个向下消息处理函数.

退出处理函数

通过调用self->link_to(other)一个强生命周期偶合的双向监听被建立.如果this 或者一个exit_msg退出,会导致系统发送exit_msg.默认情况除非退出原因是exit_reason::normal,actors在接收到一个exit_msg后将停止.这个机制在actor系统中传播失败状态.从一个子系统中一个错误将导致所有连接的actors共同失败.Actors可以通过调用set_exit_handler(f)重写默认的处理函数, fvoid (exit_message&) 或者 void (scheduled_actor*, exit_message&).

错误处理函数

当处理函数返回一个error,Actors发送错误消息给其它的actor.与退出消息相似,错误消息通常造成接收的actor终止.除非通过set_error_handler(f)一个自定义的函数被设置, f是签名为void (error&) 或者 void (scheduled_actor* error&)的函数对象.另外,request的第二个参数来设置一个错误处理函数为一个指定的请求(见请求的错误处理).如果request没有错误处理函数,则默认的处理函数会被使用.

默认处理函数

每当一个actor的行为没有被匹配那么默认的处理函数将被调用.Actors可以调用set_default_handler改变默认的处理函数.期望的函数签名是result<message> (scheduled*,message_view&),忽略self指针.默认的处理函数能回复消息或者导致运行系统跳过输入消息而允许一个actor去处理它在后来的状态.CAF提供下列内置的实现:reflect,reflect_and_quit,print_and_drop, dropskip.前两个的目的是用来调试和测试和允许一个actor去简单地返回一个输入.接下来的两个函数终止包括或未包括打印一个事先警告的未预料的消息.最后skip忽略一个输入消息在邮槽中.默认为print_and_drop.

请求

CAF的一个主要的特性是它能够通过类型系统联合输入和输出类型.举例来说,一个typed_actor<replies_to<int>::with<int>>本质上同一个函数.它接收一个int类型作为输入和返回另一个int.CAF通过该功能来简单的从消息处理函数的结构创建一个响应消息.允许CAF去匹配请求响应消息和提供一个方便的API用这种风格通讯.

发送请求和处理回复

Actors通过调用request(receiver, timeout, content...)来发送请求消息这个函数返回一个允许actor去设置一个只处理一次回复消息的处理函数的中间件.基于事件的actor使用request(...).then或者request(...).await.前一个异步处理常规的actor的一次性请求和回复.后一个阻塞常规的actor行为直到以后进先出的顺序来处理回复到达.阻塞的 actors总是使用request(...).receive, 阻塞直到一次性处理函数被调用.Actors接收一个sec:request_timeout(见系统错误代码)错误消息(见).Actors如果发生一个超时则接收到一个sec::request_timeout(见系统错误代码)错误消息(见错误处理).用记可以通过设置infinite做到无限超时.只建议使用在接收者为本地时.

在下列样例中,我们使用简单的cell actors展示终端通讯.

using cell = typed_actor<reacts_to<put_atom, int>,
                         replies_to<get_atom>::with<int>>;

struct cell_state {
  int value = 0;
};

cell::behavior_type cell_impl(cell::stateful_pointer<cell_state> self, int x0) {
  self->state.value = x0;
  return {
    [=](put_atom, int val) {
      self->state.value = val;
    },
    [=](get_atom) {
      return self->state.value;
    }
  };
}

第一部分的代码说明基于事件actors如何使用then或者await.

void waiting_testee(event_based_actor* self, vector<cell> cells) {
  for (auto& x : cells)
    self->request(x, seconds(1), get_atom::value).await([=](int y) {
      aout(self) << "cell #" << x.id() << " -> " << y << endl;
    });
}

void multiplexed_testee(event_based_actor* self, vector<cell> cells) {
  for (auto& x : cells)
    self->request(x, seconds(1), get_atom::value).then([=](int y) {
      aout(self) << "cell #" << x.id() << " -> " << y << endl;
    });
}

第二部分的代码展示了一个阻塞的actorreceive的使用.注意阻塞的actors没有特定的错误处理函数因此要求传递一个错误处理函数的回调来处理返回消息.

void blocking_testee(blocking_actor* self, vector<cell> cells) {
  for (auto& x : cells)
    self->request(x, seconds(1), get_atom::value).receive(
      [&](int y) {
        aout(self) << "cell #" << x.id() << " -> " << y << endl;
      },
      [&](error& err) {
        aout(self) << "cell #" << x.id()
                   << " -> " << self->system().render(err) << endl;
      }
    );
}

我们产生5个cells和参数为0, 1, 4, 9, 和16

vector<cell> cells;
for (auto i = 0; i < 5; ++i)
  cells.emplace_back(system.spawn(cell_impl, i * i));

当传递cellsvector到我们三个不同的实现,我们观察三个的输出,我们的waiting_testeeactor将总是打印

cell #9 -> 16
cell #8 -> 9
cell #7 -> 4
cell #6 -> 1
cell #5 -> 0

这是因为await将一次性处理了函数压入栈中,按照后进先出的顺序来处理来到的回复消息.

multiplexed_testee实现无法确定打印的顺序.回复消息以任意的顺序到达,并且立即执行.

最后,blocking_testee实现总是打印:

cell #5 -> 0
cell #6 -> 1
cell #7 -> 4
cell #8 -> 9
cell #9 -> 16

两种基于事件的actors设置一系列的一次性处理函数,然后从实现函数中返回来处理所有的请求.与之相比,阻塞的函数在发送其它请求之前将等待一个回复到达.

请求内的错误处理

请求机制允许CAF明确地关联请求和回复消息.同样适用于回复一个错误消息.因此,CAF允许添加一个错误的处理函数做为thenawait的第二个可选参数(这个参数对于receive是强制性的).如果没有定义这样的处理函数,默认的处理函数(见错误处理函数)被使用调用.

以下例子,我们考虑一个简单的返回一个除零的错误.这个例子使用一个自定义的错误类别(见错误)

enum class math_error : uint8_t {
  division_by_zero = 1
};

error make_error(math_error x) {
  return {static_cast<uint8_t>(x), atom("math")};
}

using div_atom = atom_constant<atom("div")>;

using divider = typed_actor<replies_to<div_atom, double, double>::with<double>>;

divider::behavior_type divider_impl() {
  return {
    [](div_atom, double x, double y) -> result<double> {
      if (y == 0.0)
        return math_error::division_by_zero;
      return x / y;
    }
  };
}

当发送一个请求给除法器, 我们使用一个自定义的错误处理报告一个错误.

scoped_actor self{system};
self->request(div, std::chrono::seconds(10), div_atom::value, x, y).receive(
  [&](double z) {
    aout(self) << x << " / " << y << " = " << z << endl;
  },
  [&](const error& err) {
    aout(self) << "*** cannot compute " << x << " / " << y << " => "
               << system.render(err) << endl;
  }
);

延迟消息

可以使用delayed_send发送一个延迟消息.以下的代码展示一个基于时间循环的例子.

// uses a message-based loop to iterate over all animation steps
void dancing_kirby(event_based_actor* self) {
  // let's get it started
  self->send(self, step_atom::value, size_t{0});
  self->become (
    [=](step_atom, size_t step) {
      if (step == sizeof(animation_step)) {
        // we've printed all animation steps (done)
        cout << endl;
        self->quit();
        return;
      }
      // print given step
      draw_kirby(animation_steps[step]);
      // animate next step in 150ms
      self->delayed_send(self, std::chrono::milliseconds(150),
                         step_atom::value, step + 1);
    }
  );
}

委派消息

Actors能通过delegate传递请求的任务.这能够使委派消息的接收者去回复通常是从消息处理函数简单地返回一个值使得源发送者能收到回复.下列图解展示了请求委派从actorB到actorC.

A                  B                  C
|                  |                  |
| ---(request)---> |                  |
|                  | ---(delegate)--> |
|                  X                  |---\
|                                     |   | compute
|                                     |   | result
|                                     |<--/
| <-------------(reply)-------------- |
|                                     X
|---\
|   | handle
|   | response
|<--/
|
X

从消息处理函数返回delegate(...)的结果,下列代码演示压制隐含的回复消息和允许编译器检查当使用静态的actors的返回类型.

void actor_a(event_based_actor* self, const calc& worker) {
  self->request(worker, std::chrono::seconds(10), add_atom::value, 1, 2).then(
    [=](int result) {
      aout(self) << "1 + 2 = " << result << endl;
    }
  );
}

calc::behavior_type actor_b(calc::pointer self, const calc& worker) {
  return {
    [=](add_atom add, int x, int y) {
      return self->delegate(worker, add, x, y);
    }
  };
}

calc::behavior_type actor_c() {
  return {
    [](add_atom, int x, int y) {
      return x + y;
    }
  };
}

void caf_main(actor_system& system) {
  system.spawn(actor_a, system.spawn(actor_b, system.spawn(actor_c)));
}

消息的优先顺序

默认情况,所有消息有相同的优先级和actors忽略优先级标志.通过使用priority_aware模板参数来产生一个带优先级的actors, 如下面代码所示, 该标志导致actor使用带优先级的邮槽实现.无法动态修改.

#include "caf/all.hpp"

using std::endl;
using namespace caf;

behavior foo(event_based_actor* self) {
  self->send(self, "world");
  self->send<message_priority::high>(self, "hello");
  // when spawning `foo` with priority_aware flag, it will print "hello" first
  return {
    [=](const std::string& str) {
      aout(self) << str << endl;
    }
  };
}

void caf_main(actor_system& system) {
  scoped_actor self{system};
  aout(self) << "spawn foo" << endl;
  self->spawn(foo);
  self->await_all_other_actors_done();
  aout(self) << "spawn foo again with priority_aware flag" << endl;
  self->spawn<priority_aware>(foo);
}

CAF_MAIN()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值