`
wooce
  • 浏览: 184735 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

asio的作者对自己实现的coroutine做的说明

    博客分类:
  • C++
 
阅读更多

Keen-eyed Asio users may have noticed that Boost 1.42 includes a new example, HTTP Server 4, that shows how to use stackless coroutines in conjunction with asynchronous operations. This follows on from the coroutines I explored in the previous three posts, but with a few improvements. In particular:

  • the pesky entry pseudo-keyword is gone; and
  • a new fork pseudo-keyword has been added.

The result bears a passing resemblance to C#'s yield and friends. This post aims to document my stackless coroutine API, but before launching into a long and wordy explanation, here's a little picture to liven things up:

[ Click image for full size. This image was generated from this source code using asioviz. ]

class coroutine

Every coroutine needs to store its current state somewhere. For that we have a class called coroutine:

class coroutine
{
public:
  coroutine();
  bool is_child() const;
  bool is_parent() const;
  bool is_complete() const;
};

Coroutines are copy-constructible and assignable, and the space overhead is a single int. They can be used as a base class:

class session : coroutine
{
  ...
};

or as a data member:

class session
{
  ...
  coroutine coro_;
};

or even bound in as a function argument using bind() (see previous post). It doesn't really matter as long as you maintain a copy of the object for as long as you want to keep the coroutine alive.

reenter

The reenter macro is used to define the body of a coroutine. It takes a single argument: a pointer or reference to a coroutine object. For example, if the base class is a coroutine object you may write:

reenter (this)
{
  ... coroutine body ...
}

and if a data member or other variable you can write:

reenter (coro_)
{
  ... coroutine body ...
}

When reenter is executed at runtime, control jumps to the location of the last yield or fork.

The coroutine body may also be a single statement. This lets you save a few keystrokes by writing things like:

reenter (this) for (;;)
{
  ...
}

Limitation: The reenter macro is implemented using a switch. This means that you must take care when using local variables within the coroutine body. The local variable is not allowed in a position where reentering the coroutine could bypass the variable definition.

yield statement

This form of the yield keyword is often used with asynchronous operations:

yield socket_->async_read_some(buffer(*buffer_), *this);

This divides into four logical steps:

  1. yield saves the current state of the coroutine.
  2. The statement initiates the asynchronous operation.
  3. The resume point is defined immediately following the statement.
  4. Control is transferred to the end of the coroutine body.

When the asynchronous operation completes, the function object is invoked and reenter causes control to transfer to the resume point. It is important to remember to carry the coroutine state forward with the asynchronous operation. In the above snippet, the current class is a function object object with a coroutine object as base class or data member.

The statement may also be a compound statement, and this permits us to define local variables with limited scope:

yield
{
  mutable_buffers_1 b = buffer(*buffer_);
  socket_->async_read_some(b, *this);
}

yield return expression ;

This form of yield is often used in generators or coroutine-based parsers. For example, the function object:

struct interleave : coroutine
{
  istream& is1;
  istream& is2;
  char operator()(char c)
  {
    reenter (this) for (;;)
    {
      yield return is1.get();
      yield return is2.get();
    }
  }
};

defines a trivial coroutine that interleaves the characters from two input streams.

This type of yield divides into three logical steps:

  1. yield saves the current state of the coroutine.
  2. The resume point is defined immediately following the semicolon.
  3. The value of the expression is returned from the function.

yield ;

This form of yield is equivalent to the following steps:

  1. yield saves the current state of the coroutine.
  2. The resume point is defined immediately following the semicolon.
  3. Control is transferred to the end of the coroutine body.

This form might be applied when coroutines are used for cooperative threading and scheduling is explicitly managed. For example:

struct task : coroutine
{
  ...
  void operator()()
  {
    reenter (this)
    {
      while (... not finished ...)
      {
        ... do something ...
        yield;
        ... do some more ...
        yield;
      }
    }
  }
  ...
};
...
task t1, t2;
for (;;)
{
  t1();
  t2();
}

yield break ;

The final form of yield is adopted from C# and is used to explicitly terminate the coroutine. This form is comprised of two steps:

  1. yield sets the coroutine state to indicate termination.
  2. Control is transferred to the end of the coroutine body.

Once terminated, calls to is_complete() return true and the coroutine cannot be reentered.

 

Note that a coroutine may also be implicitly terminated if the coroutine body is exited without a yield, e.g. by returnthrow or by running to the end of the body.

fork statement ;

The fork pseudo-keyword is used when "forking" a coroutine, i.e. splitting it into two (or more) copies. One use of fork is in a server, where a new coroutine is created to handle each client connection:

reenter (this)
{
  do
  {
    socket_.reset(new tcp::socket(io_service_));
    yield acceptor->async_accept(*socket_, *this);
    fork server(*this)();
  } while (is_parent());
  ... client-specific handling follows ...
}

The logical steps involved in a fork are:

  1. fork saves the current state of the coroutine.
  2. The statement creates a copy of the coroutine and either executes it immediately or schedules it for later execution.
  3. The resume point is defined immediately following the semicolon.
  4. For the "parent", control immediately continues from the next line.

The functions is_parent() and is_child() can be used to differentiate between parent and child. You would use these functions to alter subsequent control flow.

Note that fork doesn't do the actual forking by itself. It is your responsibility to write the statement so that it creates a clone of the coroutine and calls it. The clone can be called immediately, as above, or scheduled for delayed execution using something likeio_service::post().

分享到:
评论

相关推荐

    asio服务.rar

    通过使用回调函数或者协程(coroutine),ASIO可以在完成I/O操作时通知用户,而不是等待操作完成。 在"易语言asio服务源码"这个压缩包中,我们可以推测包含的是用易语言编写的、基于ASIO实现的服务端代码。易语言是...

    boost-asio-cpp-network-programming.zip_BOOST教程_Boost_boost asio

    Boost.Asio的异步编程模型是基于回调的,但也支持C++11的`std::future`和`std::async`,提供更现代的协程(coroutine)支持。异步操作的启动通常通过调用一个启动函数,如`async_read`或`async_write`,并在完成时...

    Boost asio 网络编程中文版

    5. **定时器**:Boost.Asio提供了多种定时器类型,如相对定时器和绝对定时器,可以用于实现超时操作、定期任务调度等。 6. **信号处理**:学习如何注册信号处理器,以优雅地响应操作系统信号,如SIGINT(Ctrl+C)或...

    asio C++ library-开源

    在文件`asio-1.18.1`中,你会找到asio库的源代码,包括头文件和实现文件。这些文件涵盖了asio的所有功能,如异步I/O操作、定时器、信号处理、线程管理和错误处理。开发者可以通过直接包含这些头文件来使用asio库,...

    LuaAsio:基于Boost.Asio和Lua协程的用于LuaJIT的简单透明的非阻塞TCP IO

    LuaAsio通过将Boost.Asio的异步操作与LuaJIT的协程结合,实现了非阻塞I/O的简洁API。用户只需编写简单的Lua代码,就可以利用底层的异步I/O操作。例如,创建一个TCP服务器可以仅用几行代码就能实现: ```lua local ...

    corotine.zip

    这意味着代码可能使用了跨平台的库或API,如Boost.Asio、Poco或者C++标准库中的`std::async`和`std::thread`等,来实现协程的底层调度和通信。 标签中提到的"windows"和"linux"暗示了该代码可能使用了特定于平台的...

    用C++做WEB开发

    异步编程通过回调函数、协程(如C++17的std::coroutine)或者future/promise模式来实现非阻塞I/O。 4. 数据库交互 Web应用通常需要与数据库交互。C++有多种数据库访问库,如SQLite(轻量级)、MySQL++、ODBC(开放...

    cpp-coroutine:用于使用Boost协程的库

    在C++中,通过Boost库可以使用定时器类(例如`boost::asio::deadline_timer`)来设置超时,然后在协程中配合这些定时器来实现相关逻辑。当超时期限时,协程可以被中断或者触发特定的行为。 “清理没人愿意‘更新’...

    casock c++ RPC 框架

    结合protobuf的高效序列化和asio的异步I/O,casock能实现低延迟、高吞吐量的RPC通信。 总之,casock是一个结合了protobuf的强大序列化能力和asio的高效网络通信的C++ RPC框架,适合构建高性能的分布式系统。对于...

    C++异步方式实现聊天程序

    1. **网络通信**:使用异步I/O模型,如Boost.Asio库或C++20中的`std::coroutine`,可以有效地处理网络连接和数据传输。这些库提供了非阻塞的读写操作,使得程序可以在等待数据时执行其他任务。 2. **线程安全**:在...

    C++ 网络编程_epub格式

    它支持多种异步操作模式,如回调函数、future、Promise和coroutine,这些使得程序员能够编写出并发性和非阻塞式的网络应用,从而提高系统性能。Asio库还包含了一些高级功能,如定时器、信号处理、序列化和名字解析...

    简易C++爬虫框架,基于多线程、多任务,快速实现网络数据爬取

    另外,虽然C++标准库不直接支持协程,但可以借助第三方库如Coroutine TS或Boost.Asio来实现,它们能提供轻量级的并发机制,让程序员更方便地控制任务的执行流程。 压缩包中的"crawler"文件可能包含了整个爬虫框架的...

    BoostAsyncSocket

    Boost.Asio库通过回调函数、事件驱动或协程(Boost.Coroutine)等方式实现了异步I/O。 Boost.Asio的核心是`io_service`对象,它是所有异步操作的调度中心。在这个例子中,`io_service`会管理所有的网络事件,如连接...

    qtnetworkng:下一代QtNetwork。 用于QtC ++的基于协程的网络框架,其API比boost :: asio更简单

    与boost :: asio和Qt的QtNetwork相比,QtNetworkNg具有更简单的API,类似于python-gevent。 顾名思义,QtNetworkNg需要Qt5框架。 有关更多详细信息,请访问: 文件资料 访问 产品特点 通用协同程序,具有与QThread...

    workflow-master.zip

    在C++中,通常会使用如Boost.Asio或C++20的std::coroutine等库来实现。这些库允许程序在等待网络操作完成时执行其他任务,提高了程序的响应性和效率。异步网络引擎特别适合于构建高可用性和高吞吐量的服务器应用,...

    深入实践Boost:Boost程序库开发的94个秘笈

    Boost.Coroutine,实现轻量级协程。 7. **算法和数据结构**:如 Boost.Graph,图算法库;Boost.Range,提供统一的接口处理序列;Boost.Foreach,方便遍历容器。 8. **数学和统计**:Boost.Math 提供了广泛的数学...

    C++计算机网络通信例子

    异步操作则可以利用回调函数、future/promise或C++17引入的协程(coroutine)来实现非阻塞的I/O。 总之,这个“C++计算机网络通信例子”提供了实践操作的宝贵机会,帮助开发者掌握C++中的网络编程技术。通过学习和...

    boost1.57源代码

    Updated Libraries: Any, Asio, Circular Buffer, Config, Container, Coroutine, Flyweight, Geometry, Interprocess, Intrusive, Iterator, Lexical Cast, Math, Move, MultiArray, Multiprecision, Multi-Index ...

    downloads.part1.rar

    boost-1.70.0.tar.gz boostorg-any-boost-1.70.0.tar.gz boostorg-array-boost-1.70.0.tar.gz boostorg-asio-boost-1.70.0.tar.gz boostorg-assert-boost-1.70.0.tar.gz boostorg-assign-boost-1.70.0.tar.gz ...

    downloads.part3.rar

    boost-1.70.0.tar.gz boostorg-any-boost-1.70.0.tar.gz boostorg-array-boost-1.70.0.tar.gz boostorg-asio-boost-1.70.0.tar.gz boostorg-assert-boost-1.70.0.tar.gz boostorg-assign-boost-1.70.0.tar.gz ...

Global site tag (gtag.js) - Google Analytics