C++20 协程的基础与实战应用

C++20 标准引入了协程(coroutines)机制,为编写异步、事件驱动和流式编程提供了语言级别的支持。本文将从协程的基本概念、核心语法、协程框架的实现原理以及实际应用场景入手,帮助读者快速上手并在项目中灵活运用协程。

1. 协程的核心概念

1.1 什么是协程?

协程是一种轻量级的线程,能够在执行过程中“挂起”和“恢复”,从而实现非阻塞式的等待和切换。与传统线程相比,协程的切换成本极低(只涉及栈帧状态的保存/恢复),而且编程模型更接近同步代码。

1.2 协程的关键字

C++20 引入了三大关键字:

  • co_await:等待一个异步操作完成,类似 await
  • co_yield:从协程中产生一个值,类似 yield
  • co_return:终止协程并返回最终结果。

1.3 协程的类型

协程在实现时需要一个 协程句柄(coroutine handle),并且根据 co_await 的返回类型决定协程的返回类型。常见的返回类型包括:

  • `std::future `:返回一个标准的 `future`。
  • `generator `:生成器,用于流式输出。
  • `task `:自定义异步任务类型。

2. 基本语法与使用示例

#include <iostream>
#include <coroutine>
#include <thread>
#include <chrono>

// 1. 一个简单的协程返回整型
std::future <int> async_add(int a, int b) {
    std::cout << "start async_add\n";
    co_await std::suspend_always{}; // 模拟异步延迟
    co_return a + b;
}

int main() {
    auto fut = async_add(3, 5);
    std::cout << "waiting for result...\n";
    std::cout << "result = " << fut.get() << '\n';
    return 0;
}

上述代码演示了一个最基本的协程:async_add 在内部挂起一次,然后返回结果。std::suspend_always 是一个协程悬挂点,实际项目中可以替换为网络 I/O、定时器等异步事件。

3. 协程生成器(Generator)

协程生成器可用于在迭代器语义下产生一系列值,类似于 Python 的 yield

template<typename T>
struct generator {
    struct promise_type {
        T current_value;
        std::suspend_always yield_value(T value) { current_value = value; return {}; }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() { std::terminate(); }
        generator get_return_object() {
            return generator{ std::coroutine_handle <promise_type>::from_promise(*this) };
        }
        void return_void() {}
    };

    std::coroutine_handle <promise_type> handle;
    explicit generator(std::coroutine_handle <promise_type> h) : handle(h) {}
    ~generator() { if (handle) handle.destroy(); }
    bool move_next() { handle.resume(); return !handle.done(); }
    T current_value() const { return handle.promise().current_value; }
};

generator <int> fibonacci(int n) {
    int a = 0, b = 1;
    for (int i = 0; i < n; ++i) {
        co_yield a;
        int temp = a + b;
        a = b; b = temp;
    }
}

int main() {
    for (auto it = fibonacci(10); it.move_next(); ) {
        std::cout << it.current_value() << ' ';
    }
    std::cout << '\n';
}

4. 协程与事件循环

协程最典型的应用场景是实现事件循环(Event Loop)。以下示例使用 asio 作为 I/O 库,演示如何在事件循环中使用协程进行网络编程。

#include <asio.hpp>
#include <iostream>
#include <coroutine>

asio::awaitable <void> echo_server(asio::ip::tcp::acceptor& acceptor) {
    for (;;) {
        auto socket = co_await acceptor.async_accept(asio::use_awaitable);
        std::cout << "client connected\n";
        co_spawn(socket.get_executor(),
            [socket = std::move(socket)]() mutable -> asio::awaitable <void> {
                char data[1024];
                std::size_t n = co_await socket.async_read_some(asio::buffer(data), asio::use_awaitable);
                co_await asio::async_write(socket, asio::buffer(data, n), asio::use_awaitable);
            }, asio::detached);
    }
}

int main() {
    asio::io_context io;
    asio::ip::tcp::acceptor acceptor(io, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), 12345));
    co_spawn(io, echo_server(acceptor), asio::detached);
    io.run();
}

5. 结合线程池实现高性能异步

C++20 协程与线程池结合,可实现高吞吐量的异步任务执行。常见做法是使用 std::async 或自定义 task 对象,将协程的结果推入线程池的任务队列。

template<typename T>
struct task {
    struct promise_type;
    using handle_type = std::coroutine_handle <promise_type>;

    handle_type handle;

    struct promise_type {
        T value;
        std::exception_ptr exc;

        auto get_return_object() { return task{ handle_type::from_promise(*this) }; }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }

        void unhandled_exception() { exc = std::current_exception(); }

        void return_value(T v) { value = v; }

        task get_return_object() { return task{ handle_type::from_promise(*this) }; }
    };

    task(handle_type h) : handle(h) {}
    ~task() { if (handle) handle.destroy(); }
    T get() {
        handle.resume();
        if (handle.promise().exc) std::rethrow_exception(handle.promise().exc);
        return handle.promise().value;
    }
};

task <int> async_square(int x) {
    co_return x * x;
}

int main() {
    auto t = async_square(7);
    std::cout << "square = " << t.get() << '\n';
}

6. 常见坑与调试技巧

场景 问题 解决方案
协程未挂起 co_await 前后无任何异步对象 确认 co_await 的对象实现了 await_ready/await_suspend/await_resume
协程生命周期 协程句柄已销毁后再使用 通过 std::shared_ptrstd::future 管理协程对象
多线程协程 直接在多个线程中使用同一个协程句柄 协程非线程安全,需使用同步机制或在各线程创建独立协程
资源泄漏 协程未正确 destroy promise_typefinal_suspend 里返回 std::suspend_always{} 并手动销毁句柄

调试协程时,可使用 -fcoroutines 编译器选项,并借助 gdbinfo coroutine 命令查看协程状态。

7. 结语

C++20 协程为开发者提供了一种更自然、更高效的异步编程模型。掌握 co_await/ co_yield/ co_return 的使用,以及协程句柄、Promise、awaitable 对象的设计思路,能够让你在网络编程、游戏引擎、并发系统等领域快速构建出高性能、可维护的代码。希望本文的示例与经验能为你开启协程之旅提供助力。

发表评论