C++20,boost协程

发布于:2024-08-02 ⋅ 阅读:(31) ⋅ 点赞:(0)

协程(Coroutines)详解

1. 协程的基本概念

  • 暂停和恢复: 协程可以在某个点暂停执行,并在稍后从暂停的地方继续执行。这与传统的函数调用不同,传统函数一旦开始执行就会一直运行到结束。
  • 异步编程: 协程非常适合处理异步操作,比如网络请求、文件I/O等。它们可以在等待I/O操作完成时暂停,从而避免阻塞线程。
  • 轻量级: 协程比线程更轻量级,因为它们不需要操作系统的线程管理,切换上下文的开销也更小。

2. 协程的执行流程

  • 启动协程: 使用 co_spawn 启动协程,并将其提交给执行器。
  • 遇到 co_await: 协程在 co_await 处暂停执行,将控制权交还给执行器。
  • 异步操作继续进行: 异步操作在后台继续进行,不会阻塞线程。执行器可以调度其他任务或协程。
  • 恢复执行: 当异步操作完成时,执行器将控制权交还给协程,协程从暂停的地方继续执行。

3. 关键概念和术语

co_await
  • 作用: 用于在协程中等待一个异步操作完成。它会暂停协程的执行,直到被等待的操作完成,然后恢复执行。
awaitable
  • 定义: 表示可以被 co_await 等待的对象,封装了异步操作的状态和结果。
  • 作用: 与 co_await 一起使用,协程会在 co_await 处暂停,直到 awaitable 对象表示的异步操作完成。
co_spawn
  • 作用: 启动一个新的协程。接受一个执行器(executor)、一个协程函数和一个分离策略(detached),并启动协程的执行。
执行器(executor)
  • 定义: 用于管理协程执行的对象。它负责调度协程的执行。在 Boost.Asio 中,io_context 就是一个执行器。
use_awaitable
  • 作用: 一个标记,用于指示异步操作应该返回一个 awaitable 对象,从而可以在协程中使用 co_await 来等待这些操作的完成。

4. 示例代码解析

echo 协程
awaitable<void> echo(ip::tcp::socket socket)
{
    try
    {
        char data[1024];
        for (;;)
        {
            std::size_t n = co_await socket.async_read_some(buffer(data), use_awaitable);
            co_await async_write(socket, buffer(data, n), use_awaitable);
        }
    }
    catch (const std::exception& e)
    {
        std::cout << "Exception is " << e.what() << std::endl;
    }
}

  • 功能: 实现一个简单的回显服务器,接收客户端发送的数据并将其原样返回。
  • 流程: 使用 co_await 等待异步读取和写入操作,通过无限循环持续处理客户端的数据,捕获并打印任何异常。
listener 协程
awaitable<void> listener()
{
    auto executor = co_await this_coro::executor;
    ip::tcp::acceptor acceptor(executor, { ip::tcp::v4(), 10086 });
    for (;;)
    {
        ip::tcp::socket socket = co_await acceptor.async_accept(use_awaitable);
        co_spawn(executor, echo(std::move(socket)), detached);
    }
}

  • 功能: 负责监听端口并接受新的TCP连接。
  • 流程: 获取当前协程的执行器,创建TCP接受器,使用 co_await 等待异步接受连接,每当接受到一个新的连接时,使用 co_spawn 启动一个新的 echo 协程来处理该连接。
main 函数
int main()
{
    try
    {
        io_context io_context(1);
        signal_set signals(io_context, SIGINT, SIGTERM);
        signals.async_wait(& 
            {
                io_context.stop();
            }
        );
        co_spawn(io_context, listener(), detached);
        io_context.run();
    }
    catch (const std::exception& e)
    {
        std::cout << "Exception is " << e.what() << std::endl;
    }
}

  • 功能: 程序的入口点,负责初始化I/O上下文并启动监听协程。
  • 流程: 创建 io_context 对象,设置信号处理器,捕捉 SIGINTSIGTERM 信号,并在接收到信号时停止 io_context,使用 co_spawn 启动 listener 协程,调用 io_context.run() 开始事件循环,处理异步操作。

5. 协程与线程的对比

  • 协程: 轻量级、用户态、非抢占式,适用于I/O密集型任务。
  • 线程: 重量级、内核态、抢占式,适用于CPU密集型任务。

6. 常见问题解答

  • 为什么在 co_spawn 中传递 coroutine() 而不是 coroutine: 传递 coroutine() 是因为 co_spawn 需要一个 awaitable 对象,而不是一个可调用对象。coroutine() 调用协程函数并返回一个 awaitable 对象。