协程(Coroutine)是 C++20 的一个重要新特性,它为异步编程提供了一种更直观、更易于维护的方式。传统的异步编程往往依赖回调、状态机或者第三方库(如 Boost.Asio、libuv 等),代码可读性差、错误易出。协程通过让函数能够挂起和恢复,隐藏了底层的状态机实现,让编写异步代码如同编写同步代码一样简单。
1. 协程的基本概念
协程是一种可挂起的函数,它在执行过程中可以暂停(co_await、co_yield 或 co_return)并保留其执行状态,随后再恢复继续执行。C++20 对协程的支持主要体现在以下几个关键字上:
co_await:挂起协程,等待异步操作完成后继续执行。co_yield:产生一个值并挂起协程,常用于生成器(generator)模式。co_return:返回值并结束协程,等价于普通函数的return。
2. 协程的实现细节
在 C++20 标准中,协程的实现依赖于 协程框架(std::coroutine_handle、std::suspend_always、std::suspend_never 等)。编译器会为协程生成一个隐藏的状态机对象,负责保存局部变量、栈帧以及返回点。以下是一个最小化协程的结构:
struct task {
struct promise_type {
task 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(); }
};
};
协程的入口是 initial_suspend,决定协程是否立即开始执行;退出点是 final_suspend,在此处协程可以执行清理工作。若想在协程内部产生异步结果,需要自定义 awaitable 类型并实现 await_ready、await_suspend、await_resume 三个成员函数。
3. 一个实战示例:异步文件读取
下面给出一个完整的示例,演示如何使用协程读取文件内容。示例使用标准库的 `
`、“,并自定义一个 `awaitable` 类型来包装文件读取操作。 “`cpp #include #include #include #include #include #include namespace fs = std::filesystem; // awaitable wrapper struct async_read { fs::path file_path; std::vector buffer; std::size_t size; std::exception_ptr exc; struct awaiter { async_read& self; bool await_ready() noexcept { return false; } void await_suspend(std::coroutine_handle h) noexcept { // 开启异步读操作 std::thread([self = &self, h]() { try { std::ifstream in(self->file_path, std::ios::binary); if (!in) throw std::runtime_error(“文件打开失败”); self->buffer.resize(self->size); in.read(self->buffer.data(), self->size); } catch (…) { self->exc = std::current_exception(); } h.resume(); // 恢复协程 }).detach(); } std::vector await_resume() { if (exc) std::rethrow_exception(exc); return std::move(buffer); } }; awaiter operator co_await() { return { *this }; } }; // 简易 task 类型 struct task { struct promise_type { task get_return_object() { return {}; } std::suspend_never initial_suspend() noexcept { return {}; } std::suspend_never final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception() { std::terminate(); } }; }; task read_file(const fs::path& path, std::size_t size) { auto data = co_await async_read{path, {}, size, nullptr}; std::cout (10, data.size()); ++i) std::cout (static_cast(data[i]))