在C++20标准中,协程(Coroutine)被正式纳入语言规范,它为异步编程提供了强大而直观的语法支持。相比传统的回调或Future机制,协程通过挂起(suspend)与恢复(resume)的语义,使得编写异步代码更加顺畅、可读性更高。本文将从实现原理、关键类以及实际使用案例三方面,详细介绍C++协程的实现与应用。
一、协程实现原理
C++协程的实现依赖于三个核心组件:
-
协程句柄(
std::coroutine_handle)
协程句柄是协程的入口点和管理对象,负责挂起、恢复和销毁协程。它内部维护了协程的状态和调用栈。 -
协程 Promise 对象
每个协程都有一个与之关联的 Promise,负责协程的生命周期管理、异常传播以及返回值处理。promise_type是协程实现中最重要的类型,定义了initial_suspend、final_suspend、get_return_object、return_value、unhandled_exception等成员。 -
协程状态机
编译器将协程转换为状态机,在每个co_await、co_yield或co_return处生成对应的状态点。挂起点会导致协程保存当前执行状态,随后返回控制权;恢复点则通过协程句柄继续执行。
二、关键标准库类型
std::future与std::promise传统异步模型- `std::generator `(可选实现)用于协程生成器
- `std::task `(实验性)用于协程任务
std::coroutine_handle与std::coroutine_traitsstd::suspend_always、std::suspend_never控制挂起策略
三、实战案例:异步文件读取
下面给出一个完整的异步文件读取示例,演示如何利用协程实现非阻塞I/O。
#include <iostream>
#include <coroutine>
#include <fstream>
#include <string>
#include <filesystem>
#include <chrono>
#include <thread>
// 简单的协程包装器
template<typename T>
struct Task {
struct promise_type {
T value_;
std::exception_ptr ep_;
Task get_return_object() {
return Task{std::coroutine_handle <promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { ep_ = std::current_exception(); }
void return_value(T value) { value_ = std::move(value); }
};
std::coroutine_handle <promise_type> coro_;
Task(std::coroutine_handle <promise_type> h) : coro_(h) {}
~Task() { if (coro_) coro_.destroy(); }
Task(const Task&) = delete;
Task(Task&& t) noexcept : coro_(t.coro_) { t.coro_ = nullptr; }
Task& operator=(const Task&) = delete;
Task& operator=(Task&& t) noexcept {
if (this != &t) {
if (coro_) coro_.destroy();
coro_ = t.coro_;
t.coro_ = nullptr;
}
return *this;
}
T get() {
if (coro_.promise().ep_)
std::rethrow_exception(coro_.promise().ep_);
return std::move(coro_.promise().value_);
}
};
Task<std::string> async_read(const std::string& path) {
// 模拟异步等待
co_await std::suspend_always{};
std::ifstream file(path, std::ios::binary);
if (!file) throw std::runtime_error("cannot open file");
std::string content((std::istreambuf_iterator <char>(file)),
std::istreambuf_iterator <char>());
co_return content;
}
int main() {
auto task = async_read("example.txt");
// 在这里我们可以做其他事情
std::cout << "正在执行其他任务...\n";
// 恢复协程
std::string data = task.get();
std::cout << "文件内容长度: " << data.size() << "\n";
}
说明
async_read在co_await std::suspend_always{}处挂起,模拟 I/O 阻塞。实际项目可替换为真正的异步 I/O 调用(如io_context、asio::async_read等)。Task负责包装协程,提供get()方法同步获取结果。main先创建任务,然后可以执行其他逻辑,最后恢复协程并获取读取结果。
四、协程使用的注意事项
-
生命周期管理
协程句柄和 Promise 对象不应在协程外部悬空,使用完毕后必须销毁。 -
异常安全
Promise 的unhandled_exception用于捕获异常。若不处理,异常会被转发到Task::get()。 -
性能开销
协程在栈上保存状态,若状态机较大可能导致堆栈开销。可使用co_yield对大数据进行分块。 -
与现有异步库结合
C++20协程可与 ASIO、libuv 等库无缝集成,使用asio::awaitable或自定义awaitable适配器。
五、总结
C++协程为异步编程提供了更直观、更强大的工具,能够让代码保持同步写法的同时实现高并发。通过理解协程的实现原理、熟悉关键库类型以及掌握实战技巧,开发者可以在大多数项目中显著提升代码可读性和执行效率。未来,协程的应用场景将进一步扩展,成为现代 C++ 开发不可或缺的一部分。