C++20 通过引入协程(Coroutines)提供了一种轻量级的异步编程模型。与传统的线程或回调机制相比,协程可以让代码保持同步风格,同时隐藏线程切换的开销。下面从基础概念、实现细节到实战案例,系统阐述如何在项目中使用协程。
1. 协程核心概念
| 关键词 | 作用 | 说明 |
|---|---|---|
co_await |
暂停协程并等待 awaiter | 让协程在 awaiter 完成前挂起,类似于 await |
co_yield |
暂停协程并返回值给调用方 | 用于生成器模式,产生一个值后挂起 |
co_return |
结束协程并返回结果 | 类似于函数返回值,但可以在任何点返回 |
| awaiter | 需要满足 Awaitable 协议的对象 | await_ready(), await_suspend(), await_resume() 三个成员函数 |
协程本质上是一个状态机。编译器会把含有 co_ 关键字的函数拆分成若干状态段,并生成一个 “promise” 对象来保存局部状态。
2. 协程与标准库的配合
C++20 标准库为协程提供了若干适配器:
std::future+std::promise:传统同步/异步接口- `std::generator `:产生值的生成器
- `std::task `:返回值为 `T` 的异步任务(在一些实现中可用)
std::async的协程版(std::async依旧是同步)
下面给出一个自定义 generator 的实现,演示 co_yield 的使用。
#include <coroutine>
#include <iostream>
template<typename T>
struct generator {
struct promise_type {
T value_;
std::suspend_always yield_value(T v) {
value_ = v;
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 unhandled_exception() { std::exit(1); }
void return_void() {}
};
using handle_t = std::coroutine_handle <promise_type>;
handle_t h_;
explicit generator(handle_t h) : h_(h) {}
~generator() { if (h_) h_.destroy(); }
generator(const generator&) = delete;
generator(generator&& other) noexcept : h_(other.h_) { other.h_ = nullptr; }
struct iterator {
handle_t h_;
bool done_ = false;
iterator(handle_t h) : h_(h) { if (h_) h_.resume(); }
iterator& operator++() {
h_.resume();
if (h_.done()) done_ = true;
return *this;
}
T operator*() const { return h_.promise().value_; }
bool operator!=(const iterator& other) const { return done_ != other.done_; }
};
iterator begin() { return iterator{h_}; }
iterator end() { return iterator{nullptr}; }
};
generator <int> range(int start, int end) {
for (int i = start; i <= end; ++i)
co_yield i;
}
int main() {
for (int x : range(1, 5))
std::cout << x << ' '; // 输出 1 2 3 4 5
}
3. 典型应用:异步网络请求
使用协程可以将网络请求写成同步风格。以下示例使用 co_await 与假设的 http_get awaiter。
#include <iostream>
#include <coroutine>
#include <string>
#include <thread>
#include <chrono>
struct http_response {
std::string body;
};
struct http_get {
std::string url;
http_get(std::string u) : url(std::move(u)) {}
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) const {
std::thread([h, url = url] {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// 模拟网络延迟
std::cout << "Fetched: " << url << '\n';
h.resume(); // 任务完成后恢复协程
}).detach();
}
http_response await_resume() const noexcept {
return { "Response body from " + url };
}
};
std::coroutine_handle<> async_main() {
http_response resp = co_await http_get("https://example.com");
std::cout << "Body: " << resp.body << '\n';
}
int main() {
auto h = async_main();
// 主线程可以做其他工作,协程在后台完成
std::this_thread::sleep_for(std::chrono::seconds(1));
}
说明:上述
http_get是一个简化示例;实际项目中可使用 Boost.Asio、cpprestsdk 等库提供的 awaiter。
4. 性能考虑
- 协程调度:协程本身是无上下文切换的,只在
co_await处挂起;真正的线程切换取决于 awaiter 的实现。 - 堆分配:每个协程需要一个堆分配的
promise对象,尽量复用或使用std::pmr来减少碎片。 - 内联优化:如果协程体很短,编译器可能把其展开为内联代码,避免额外栈帧。
5. 进一步阅读
- 《C++20 进阶》, 刘汝佳
- 《Effective Modern C++》, Scott Meyers(第六章关注协程)
- 官方文档: cppreference.com
结语
协程是 C++20 引入的最具革命性的特性之一,它把异步编程的“破碎”变成同步式代码的自然延伸。掌握了基本的 co_await / co_yield 用法后,你可以轻松将它们嵌入到网络、IO、游戏循环等多种场景,实现高效、可读性强的异步程序。祝你编码愉快!