C++20协程的基本用法与实践

在C++20中,协程(coroutine)提供了一种轻量级的、可暂停的函数机制,使得异步编程和生成器模式得以简洁实现。本文将从协程的基础概念、核心关键词、实现步骤以及典型应用场景展开,帮助你快速上手并在项目中灵活运用。

1. 协程的核心概念

关键词 说明
co_await 暂停协程,等待异步操作完成后恢复
co_yield 暂停协程,返回一个值给调用者
co_return 结束协程,返回最终结果
std::suspend_always / std::suspend_never 控制协程暂停行为的策略
std::coroutine_handle 对协程内部状态的句柄,负责手动管理生命周期

协程本质上是一段被拆分为若干状态机的函数,编译器根据 co_* 关键字生成状态机的实现。调用者与协程之间通过句柄(handle)进行交互,决定何时恢复执行。

2. 基本示例:异步文件读取

#include <iostream>
#include <coroutine>
#include <string>
#include <fstream>
#include <filesystem>

struct AsyncFileReader {
    struct promise_type {
        std::string result;
        AsyncFileReader get_return_object() { return {}; }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };
};

AsyncFileReader read_file(const std::string& path) {
    std::ifstream file(path);
    if (!file) {
        std::cerr << "Cannot open file: " << path << '\n';
        co_return;
    }
    std::string line;
    while (std::getline(file, line)) {
        co_yield line; // 每行返回一次
    }
}

int main() {
    auto reader = read_file("example.txt");
    auto handle = std::coroutine_handle<AsyncFileReader::promise_type>::from_promise(reader);
    while (!handle.done()) {
        handle.resume(); // 恢复协程
    }
}

说明
该示例展示了如何使用协程实现文件行的逐行读取。co_yield 用于返回每一行内容,主循环通过 handle.resume() 逐步获取下一行。

3. 生成器模式的典型实现

协程是实现生成器的天然工具,下面给出一个生成斐波那契数列的协程:

#include <iostream>
#include <coroutine>

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

    Generator(handle_type h) : coro(h) {}
    ~Generator() { if (coro) coro.destroy(); }

    T next() {
        coro.resume();
        return coro.done() ? T{} : coro.promise().current_value;
    }

    struct promise_type {
        T current_value;
        Generator get_return_object() { return Generator{handle_type::from_promise(*this)}; }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        std::suspend_always yield_value(T value) {
            current_value = value;
            return {};
        }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };
};

Generator <int> fib(int n) {
    int a = 0, b = 1;
    for (int i = 0; i < n; ++i) {
        co_yield a;
        int tmp = a + b;
        a = b;
        b = tmp;
    }
}

int main() {
    for (int i = 0; i < 10; ++i) {
        std::cout << fib(10).next() << ' ';
    }
}

4. 协程与异步 IO

C++20 的协程配合 std::experimental::net(或 Boost.Asio 等库)可以实现高效的异步网络程序。核心思路是:

  1. 异步操作返回协程类型
    如 `awaitable

    `,内部实现为 `std::coroutine_handle` 与 IO 库的回调机制结合。
  2. co_await 与 I/O 事件
    在协程中使用 co_await 使协程挂起,等待事件触发后再恢复。

示例:使用 Boost.Asio 的 co_spawn

#include <boost/asio.hpp>
#include <boost/asio/experimental/awaitable.hpp>

boost::asio::awaitable <void> async_echo(boost::asio::ip::tcp::socket socket) {
    char data[1024];
    std::size_t n = co_await socket.async_read_some(boost::asio::buffer(data), boost::asio::use_awaitable);
    co_await boost::asio::async_write(socket, boost::asio::buffer(data, n), boost::asio::use_awaitable);
}

int main() {
    boost::asio::io_context io_context;
    boost::asio::ip::tcp::acceptor acceptor(io_context, {boost::asio::ip::tcp::v4(), 12345});
    for (;;) {
        auto socket = co_spawn(io_context, acceptor.async_accept(), boost::asio::use_awaitable);
        co_spawn(io_context, async_echo(std::move(socket)), boost::asio::detached);
    }
}

5. 常见陷阱与调试技巧

陷阱 解决方案
协程句柄误销毁 确认 handle.done() 前不要手动销毁句柄
内存泄漏 协程内使用裸指针时,确保在 final_suspend 里清理
性能问题 过度使用 co_yield 产生大量状态切换,尽量在合适粒度下使用

调试协程可通过输出协程状态、使用 std::coroutine_handle::promise() 打印内部数据,或使用现代 IDE 的协程调试插件。

6. 结语

C++20 协程为 C++ 开发者提供了一种更直观、资源友好的方式来处理异步与生成器任务。掌握 co_awaitco_yieldco_return 的使用,以及协程句柄生命周期管理,是在项目中高效使用协程的关键。随着标准库与第三方库对协程支持的完善,未来协程将成为 C++ 开发中不可或缺的一环。祝你在协程的世界里玩得开心!

发表评论