C++20 协程的实现与应用

在 C++20 标准中,协程(coroutine)被正式纳入标准库,为异步编程提供了强大的工具。相比传统的回调、线程或事件循环,协程可以让代码保持同步写法,同时在执行过程中暂停和恢复,从而实现高效、可读性更好的异步代码。本文将从实现原理、关键类、典型使用场景以及性能注意事项等方面,系统阐述 C++20 协程的核心概念与实践。

1. 协程的基本概念

1.1 何为协程?

协程是一种轻量级的执行单元,允许在函数内部随时挂起(co_awaitco_yieldco_return)并在需要时恢复执行。不同于线程,协程在同一线程中切换,其上下文切换成本极低。

1.2 协程的生命周期

步骤 说明
构造 调用协程函数时会生成一个 promise 对象与 handlestd::coroutine_handle)。
挂起 第一次遇到 co_awaitco_yield 时会挂起,返回 handleresume() 可以继续。
恢复 resume() 调用后,协程从挂起点继续执行。
完成 执行到 co_return 或抛出异常,协程结束,handle 变为空。

2. 关键类型与宏

类型 作用 典型使用方式
`std::coroutine_handle
| 表示协程句柄,负责挂起/恢复 |auto h = handle.resume();`
std::suspend_always / std::suspend_never 控制协程是否挂起 co_await std::suspend_always{};
`std::generator
(实验性) | 用于co_yield的生成器 |for (auto x : gen) {}`
std::async + std::future 传统异步工具 `std::future
f = std::async([]{…});`

3. 协程的实现细节

3.1 Promise 与 Awaiter

  • Promise:协程函数返回的隐藏类型,负责在协程创建时存储返回值、异常以及协程状态。
  • Awaiter:实现 await_ready()await_suspend()await_resume() 接口,决定协程是否挂起以及挂起时的行为。

示例 Awaiter 结构:

template<typename T>
struct Awaiter {
    T value;
    bool await_ready() const noexcept { return false; }
    void await_suspend(std::coroutine_handle<> h) const noexcept { /* 异步任务开始 */ }
    T await_resume() const noexcept { return value; }
};

3.2 协程框架的调用链

main() -> corofunc()
          -> Promise() -> handle
          -> co_await Awaiter
          -> suspend -> return to main
          ... resume ...

4. 典型使用场景

4.1 网络 I/O

使用 asio 或自定义事件循环,结合协程可以将异步读写写成同步代码:

async_tcp_client::read_line() -> std::string {
    std::vector <char> buffer(1024);
    std::size_t n = co_await async_read(socket, buffer);
    std::string line(buffer.begin(), buffer.begin() + n);
    co_return line;
}

4.2 并行任务调度

借助协程与协程池,可以在单线程内并行执行多任务,避免线程上下文切换成本。

generator <int> worker(int id) {
    for (int i = 0; i < 10; ++i) {
        co_yield id * i;
        co_await std::suspend_always{};
    }
}

4.3 流式数据处理

使用 `std::generator

` 可对大数据流进行惰性求值,降低内存占用。 “`cpp std::generator primes() { int n = 2; while (true) { if (is_prime(n)) co_yield n; ++n; } } “` ## 5. 性能注意事项 1. **避免过度挂起**:频繁 `co_await` 可能导致性能损失,尤其是同步等待时。尽量将长时间等待放在单独任务中。 2. **对象大小**:协程对象需要存储 promise、awaiter 等,过大会导致栈溢出。使用 `co_yield` 时,generator 的 `value_type` 需考虑内存对齐。 3. **异常安全**:异常会在协程结束时抛出,务必在 promise 中正确处理 `unhandled_exception()`。 4. **调试支持**:IDE 对协程的调试支持仍有限,建议使用 `-fcoroutines` 并开启 `-fno-inline` 以便追踪。 ## 6. 代码示例:异步文件读取 “`cpp #include #include #include #include struct async_read_result { std::string data; struct awaiter { async_read_result* self; bool await_ready() const noexcept { return false; } void await_suspend(std::coroutine_handle h) const noexcept { // 异步读取文件 std::thread([self, h]{ std::ifstream file(“big.txt”); self->data.assign((std::istreambuf_iterator (file)), std::istreambuf_iterator ()); h.resume(); }).detach(); } async_read_result await_resume() const noexcept { return *self; } }; auto operator co_await() const noexcept { return awaiter{const_cast(this)}; } }; async_read_result read_file_async() { async_read_result res; co_return res; } int main() { auto task = read_file_async(); std::string content = co_await task; // 只在协程上下文中使用 std::cout 说明:上述示例使用 `co_await` 对异步读取进行封装,真正的 I/O 任务在后台线程完成,主线程可以继续执行其他操作。 ## 7. 结语 C++20 协程为 C++ 开发者提供了一套统一、轻量且高效的异步编程工具。通过熟练掌握 promise/awaiter 的机制、正确使用 `std::suspend_always` 等暂停策略以及合理设计任务调度,程序员可以在保持代码同步可读性的同时,实现高性能的并发与异步逻辑。随着标准库的进一步完善,协程将在未来的 C++ 生态中扮演更为重要的角色。

发表评论