协程(Coroutines)是 C++20 标准引入的一种轻量级协作式多任务机制。它与传统的线程相比,具有更低的创建销毁开销、更高的执行效率以及更易于书写异步代码的优势。下面我们从概念、实现细节以及实际使用场景三方面深入探讨,帮助你了解如何使用协程来替代传统线程。
1. 协程概念回顾
- 协程与线程的区别
- 线程:系统级调度,具备独立的栈空间;线程切换涉及上下文保存、调度器调度,开销大。
- 协程:用户级调度,基于单线程的执行上下文切换;切换只需保存寄存器和局部变量,开销极低。
- 协程的核心
- 悬挂点(
co_await):协程在此点暂停,等待某个异步事件完成。 - 恢复点(
co_return/co_yield):协程恢复执行,返回结果或生成值。 - 协程句柄(
std::coroutine_handle):用于手动控制协程的生命周期和恢复。
- 悬挂点(
2. 基础语法与示例
下面给出一个最小的协程示例,展示协程如何暂停与恢复:
#include <iostream>
#include <coroutine>
#include <thread>
#include <chrono>
// 1. 协程的 promise type
struct simple_promise {
int value = 0;
simple_promise() {}
~simple_promise() {}
// 协程入口
auto get_return_object() { return std::coroutine_handle <simple_promise>::from_promise(*this); }
// 进入协程时调用
std::suspend_never initial_suspend() { return {}; }
// 协程退出时调用
std::suspend_always final_suspend() noexcept { return {}; }
// 返回值
void return_value(int v) { value = v; }
// 异常处理
void unhandled_exception() { std::terminate(); }
};
using simple_coroutine = std::coroutine_handle <simple_promise>;
int main() {
// 2. 协程体
auto coro = []() -> simple_coroutine {
std::cout << "协程开始\n";
co_await std::suspend_always(); // 暂停
std::cout << "协程恢复\n";
co_return 42;
}();
// 3. 主线程控制
std::cout << "主线程等待\n";
coro.resume(); // 恢复协程
std::cout << "主线程继续\n";
coro.destroy(); // 释放资源
return 0;
}
输出
主线程等待 协程开始 协程恢复 主线程继续
关键点说明
co_await std::suspend_always()使协程暂停,控制权返回给调用者。coro.resume()恢复协程执行,直到下一个暂停点。coro.destroy()释放协程占用的资源。
3. 协程替代线程的典型场景
| 场景 | 传统实现 | 协程实现 |
|---|---|---|
| IO 密集型 | 多线程 + 阻塞 IO | 单线程 + 非阻塞 IO + co_await |
| 任务调度 | std::async / std::thread |
std::async + co_await / 自定义调度器 |
| 状态机 | 复杂 if/else + 回调 | co_yield 生成状态流 |
| 并发队列 | 生产者消费者 | 协程生成/消费,使用 co_yield 共享 |
例子:协程版异步文件读取
#include <iostream>
#include <coroutine>
#include <fstream>
#include <string>
struct async_line {
struct promise_type {
std::string line;
std::string current;
std::ifstream file;
async_line get_return_object() { return async_line{std::coroutine_handle <promise_type>::from_promise(*this)}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle <promise_type> coro;
bool next() {
if (!coro.done()) coro.resume();
return !coro.done();
}
std::string value() { return coro.promise().current; }
};
async_line read_lines(const std::string& filename) {
std::ifstream file(filename);
std::string line;
while (std::getline(file, line)) {
co_yield line; // 暂停并返回一行
}
}
使用方式:
int main() {
for (auto line : read_lines("sample.txt")) {
std::cout << line << '\n';
}
}
与传统线程对比
- 创建销毁成本:协程的创建几乎没有额外栈空间,销毁是
coro.destroy(),比std::thread低得多。 - 上下文切换:协程切换仅保存寄存器,耗时微秒;线程切换需内核态切换,耗时毫秒。
- 调度器灵活:你可以自定义事件循环,按需执行协程;线程受限于 OS 的调度策略。
4. 协程的常见陷阱
| 错误 | 说明 | 解决方案 |
|---|---|---|
忘记 resume() |
协程会一直挂起 | 确认每个 co_await 之后都有 resume() |
| 使用悬挂对象的生命周期 | co_await 期间对象已析构 |
确保协程句柄存活,或使用 std::shared_ptr |
| 异常未处理 | 协程内部异常会导致程序崩溃 | 在 promise_type::unhandled_exception() 处理或 try/catch |
| 无事件循环 | 协程挂起但没有恢复点 | 需要事件循环或手动调用 resume() |
5. 未来展望
- 协程与多核并行:C++23 继续完善协程与
std::thread的结合,提供更高层的并行 API。 - 异步 I/O 集成:与
std::experimental::net或第三方库(如 Boost.Asio)无缝配合,实现真正的“异步 I/O”。 - 协程调试工具:IDE 生态逐步加入协程调试器,帮助定位
co_await的执行路径。
6. 小结
- 协程是 C++20 的核心异步编程技术,能够在单线程或轻量级多线程环境下实现高并发。
- 它通过
co_await/co_yield提供天然的挂起与恢复机制,显著降低上下文切换成本。 - 在 IO 密集型、状态机、任务调度等场景中,协程往往能替代传统线程,代码更简洁、性能更优。
实践建议:在已有多线程项目中,先从单线程协程试点开始(如网络请求、文件 IO),逐步将关键路径迁移到协程,验证性能提升后再扩展到多线程协程混合模式。祝你在 C++20 协程世界里玩得开心!