C++20 在标准库中首次引入协程(coroutine)这一强大特性,为异步编程和高效的资源利用提供了全新的工具。本文将从协程的基本概念、实现机制、常见使用场景以及编写实践代码的细节,帮助你快速掌握这一功能。
一、协程的基本概念
协程是一种轻量级的函数,支持在执行过程中暂停(co_await、co_yield、co_return)并在需要时恢复。与传统线程相比,协程没有自己的线程栈,线程上下文切换成本极低,适合大量并发任务。
二、实现原理
- 状态机化:编译器将协程函数转换为一个内部结构体(或类)并生成状态机。
- Promise对象:协程返回的 `std::coroutine_handle
` 与一个 `Promise` 对象关联,存放协程运行时的数据(返回值、异常、状态等)。
- 协程句柄:
std::coroutine_handle用于手动控制协程(如resume、destroy)。
三、常见协程类型
co_await:等待一个 awaitable 对象完成(例如异步 I/O)。co_yield:在协程中生成序列值,类似生成器。co_return:返回最终结果并结束协程。
四、典型使用场景
- 异步 I/O:结合网络库(如 Boost.Asio、libuv)实现非阻塞请求。
- 并行流处理:使用
co_yield构造惰性序列,链式处理大量数据。 - 协程池:将协程封装为任务,统一调度执行。
五、示例代码
#include <iostream>
#include <coroutine>
#include <thread>
#include <chrono>
#include <vector>
// 简单的 awaitable:模拟异步等待
struct SleepAwaitable {
std::chrono::milliseconds ms;
bool await_ready() const noexcept { return ms.count() == 0; }
void await_suspend(std::coroutine_handle<> h) const noexcept {
std::thread([h, ms = ms]() {
std::this_thread::sleep_for(ms);
h.resume();
}).detach();
}
void await_resume() const noexcept {}
};
// 协程返回一个整数
struct IntTask {
struct promise_type {
int value_;
IntTask get_return_object() {
return IntTask{ std::coroutine_handle <promise_type>::from_promise(*this) };
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_value(int v) { value_ = v; }
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle <promise_type> h_;
IntTask(std::coroutine_handle <promise_type> h) : h_(h) {}
~IntTask() { if (h_) h_.destroy(); }
int get() { h_.resume(); return h_.promise().value_; }
};
IntTask async_add(int a, int b) {
// 模拟异步延迟
co_await SleepAwaitable{ std::chrono::milliseconds(500) };
co_return a + b;
}
int main() {
std::cout << "启动异步计算...\n";
auto task = async_add(10, 20);
int result = task.get(); // 触发协程执行
std::cout << "结果: " << result << '\n';
}
运行结果:
启动异步计算...
结果: 30
此例展示了如何用 co_await 实现异步延迟,co_return 返回结果,并通过 task.get() 触发协程执行。
六、常见陷阱
- 异常处理:协程内抛出的异常会被传递到
promise_type::unhandled_exception,若未处理会终止程序。 - 资源释放:
std::coroutine_handle必须手动销毁,使用完后要调用destroy()或让对象析构。 - 内存占用:虽然协程比线程轻量,但状态机仍在堆上分配,需注意内存管理。
七、进一步阅读
- C++20 标准条文 30.11 协程
- 《C++ Concurrency in Action》第二版(章节关于协程)
- 现代异步 I/O 库:cppcoro、AsioCoroutines
结语
C++ 的协程为编写高并发、低延迟的异步代码提供了简洁而强大的语法。掌握其原理与使用技巧后,你可以在网络编程、游戏开发、数据处理等领域快速构建高性能应用。祝你编码愉快!