coroutine 的两种实现

前面介绍了 coroutine 的概念,下面我们来看看两者实现的机理。

stackless coroutine

我们先看看 boost.asio 的实现,代码来自这里

class coroutine {
public:
  coroutine() : value_(0) {}
private:
  friend class coroutine_ref;
  int value_;
};

class coroutine_ref {
public:
  coroutine_ref(coroutine& c) : value_(c.value_) {}
  coroutine_ref(coroutine* c) : value_(c->value_) {}
  operator int() const { return value_; }
  int operator=(int v) { return value_ = v; }
private:
  int& value_;
};

这里 coroutine 是对 int 的简单封装,它只支持存储,但不支持修改和读取。对这个 int 的修改和读取是交由 coroutine_ref 这个类实现的,它通过 operator= 与对 int 的隐式转换搞定了这两个功能。为了在实际中应用这个 coroutine,我们需要一些 macro

#define reenter(c) \
  switch (coroutine_ref _coro_value = c)

#define entry \
  extern void you_forgot_to_add_the_entry_label(); \
  bail_out_of_coroutine: break; \
  case 0

#define yield \
  if ((_coro_value = __LINE__) == 0) \
  { \
    case __LINE__: ; \
    (void)&you_forgot_to_add_the_entry_label; \
  } \
  else \
    for (bool _coro_bool = false;; \
         _coro_bool = !_coro_bool) \
      if (_coro_bool) \
        goto bail_out_of_coroutine; \
      else

使用这个 coroutine 的类一般需要 private 继承 coroutine,这样对象里面藏有一个整数用来表示状态,那么一个方法里面首先需要通过 reenter(this) 这种方式产生一个 coroutine_ref 对象,用来读取这个状态。这样后面可以如下写:

reenter (this) {
entry:
  for (;;) {
    yield acceptor_.async_accept (*socket_, *this);

    while (!ec) {
      yield socket_->async_read_some (buffer(*data_), *this);
      if (ec) break;
      yield async_write(*socket_,
         buffer(*data_, length), *this);
    }
    socket_->close();
  }
}

这里本质上是一个 switch-case 的结构,在一般情况下会进入 for-loop,然后通过 yield,注意 yield 里面有个 __LINE__ 宏展开成为调用 yield 行的行号,因此每个 yield 调用都会为 _coro_value 赋予不同的值。我们可以看出来,使用 yield 调用的方法第二次进入时,由于值发生了改变,跳入到 if 里面的 case,不过注意因为每句话都没有 break,所以就会执行后面的东西了。说实在的可能还是需要对 asio 应用的环境更熟悉一些才能理解清楚为什么要设计成这样。但是似乎这个 pattern 比较有局限性,感觉似乎只是为了多线程调用时使用。可能看一些简单的实现更能理解清楚这个 idea 的来源,比如 C 通过 function 里面的 static variable 记录位置,通过 goto 跳转到合适的位置的策略换到 C++ 里面就是在对象里面嵌入状态成员了。另外注意这里利用了所谓的 Duff’s device:可以在 switch-case 里面嵌入正常的一个 control structure,并且还可以使用 goto 跳入。我们这里里面嵌入了几条 if-else。

stackful coroutine

这个实现却更让人费解,这个 coroutine 使用了 CRTP 实现的 move 语义以及 pImpl,大致的形式如下:

template<typename Signature,
         typename ContextImpl = detail::default_context_impl>
class coroutine : public movable<coroutine<Signature, ContextImpl> > {
  // ...
} ;

Signature 指函数调用的形式,而 ContextImpl 对应的是不同系统下不同的 context 的实现。这里的 context 的实现要求有一个 switching context 的功能,这本质上是要求系统(如 C API)支持 continuation。因此这个实现往往只能在特定的平台下使用。

小结

coroutine 这个概念在解决 caller-callee 不对称问题上有很大的便捷性:通常写 caller 要比 callee 容易,通过 coroutine 的介入可以简化这种关系,使得我们能通过简单的 caller 的形式实现互相调用的几个 routine 之间的关系。当然特定的问题这并不见得是唯一的解决方案。

——————
And she hurried, and emptied her pitcher into the trough, and ran again to the well to draw water, and drew for all his camels.

Advertisements
coroutine 的两种实现

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s