C++20 通过引入协程(coroutine)提供了一种简洁高效的异步编程模型。与传统基于回调或线程池的方式相比,协程在可读性、性能和资源占用上都有显著优势。本文将从基本概念、关键字、实现原理、使用场景以及常见陷阱等方面,为你拆解协程的核心价值,并给出完整示例代码,帮助你快速上手。
1. 协程基础
协程是一种轻量级的“挂起”函数。它允许函数在执行过程中暂停(co_await/co_yield/co_return)并恢复,从而在单线程中实现非阻塞异步操作。
核心关键字:
co_await:等待一个异步结果,函数挂起。co_yield:产生一个值,函数挂起。co_return:返回协程结果,函数挂起。
C++20 标准库提供了 std::future、std::promise 等协程相关类型,C++23 扩展了 std::generator 等。
2. 协程实现原理
协程在编译阶段被拆分为:
- 状态机:把函数拆成若干状态。
- promise_type:协程的执行上下文,负责存储协程结果、异常等。
- awaiter:决定何时挂起/恢复。
编译器通过 co_await 的返回值(awaitable)的 await_ready、await_suspend、await_resume 方法,控制协程生命周期。
3. 常见使用场景
| 场景 | 传统实现 | 协程实现 | 说明 |
|---|---|---|---|
| 网络请求 | 线程池 + 线程同步 | co_await async_read/write |
只需几行代码,避免回调地狱 |
| 并发流处理 | std::async + 未来 |
`generator | |
| read_lines()` | 逐行读取不占用额外线程 | ||
| UI 更新 | 信号槽 + 线程 | co_await + dispatch_to_main() |
直接在主线程恢复 |
4. 示例代码
4.1 简单异步读取文件
#include <iostream>
#include <fstream>
#include <coroutine>
#include <string>
#include <vector>
#include <thread>
#include <future>
struct FileReadAwaiter {
std::string path;
std::string result;
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) {
std::thread([h, this]() {
std::ifstream file(path);
std::string line, content;
while (std::getline(file, line)) content += line + '\n';
result = content;
h.resume(); // 恢复协程
}).detach();
}
std::string await_resume() const noexcept { return result; }
};
FileReadAwaiter async_read_file(const std::string& path) {
return FileReadAwaiter{path};
}
struct AsyncTask {
struct promise_type {
AsyncTask get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};
async void read_and_print(const std::string& path) {
std::string content = co_await async_read_file(path);
std::cout << "File content:\n" << content;
}
int main() {
read_and_print("test.txt");
std::this_thread::sleep_for(std::chrono::seconds(1)); // 等待异步线程完成
}
4.2 生成器:逐行读取文件
#include <iostream>
#include <fstream>
#include <coroutine>
#include <string>
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 {}; }
generator get_return_object() {
return generator{std::coroutine_handle <promise_type>::from_promise(*this)};
}
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle <promise_type> h;
generator(std::coroutine_handle <promise_type> h) : h(h) {}
~generator() { if (h) h.destroy(); }
generator(const generator&) = delete;
generator& operator=(const generator&) = delete;
generator(generator&& other) noexcept : h(other.h) { other.h = nullptr; }
generator& operator=(generator&& other) noexcept {
if (this != &other) { if (h) h.destroy(); h = other.h; other.h = nullptr; }
return *this;
}
bool next() {
if (!h.done()) h.resume();
return !h.done();
}
T value() const { return h.promise().current_value; }
};
generator<std::string> read_lines(const std::string& path) {
std::ifstream file(path);
std::string line;
while (std::getline(file, line)) co_yield line;
}
int main() {
for (auto g = read_lines("test.txt"); g.next(); ) {
std::cout << g.value() << '\n';
}
}
5. 性能与资源对比
| 指标 | 传统线程池 | 协程 | 备注 |
|---|---|---|---|
| CPU 开销 | 线程切换、上下文切换 | 状态机恢复 | 协程无线程切换 |
| 内存占用 | 每线程 1-2 MB | 协程栈默认 1 KB | 协程轻量 |
| 编码复杂度 | 回调链/Future 链 | 直线代码 | 可读性提升 |
6. 常见陷阱
- 忘记
co_return或return:协程默认会在函数末尾自动返回co_return,但如果提前return,可能导致协程提前结束。 co_await的 awaitable 需要满足三步协议:await_ready、await_suspend、await_resume。- 异常传播:异常会通过
promise_type::unhandled_exception传递,需要捕获。 - 协程生命周期:协程句柄必须在协程结束后销毁,否则会泄漏资源。
7. 进阶阅读
- 《C++20 协程与异步编程》
- 《Boost.Coroutine2 与 C++20 协程》
- 《实战 C++20 并发》
小结
协程是 C++20 为解决异步编程痛点而推出的强大工具。通过简化异步控制流、提升性能并降低资源占用,它已成为现代 C++ 开发不可或缺的手段。掌握基本语法与实现细节后,你可以在网络、文件 I/O、UI 线程等多种场景中灵活使用协程,让代码更清晰、更高效。