协程(Coroutines)是 C++20 标准新增的一项功能,旨在让异步编程变得更直观、更高效。它们可以在执行过程中“挂起”并在稍后恢复,内部维护一个状态机,从而让代码保持同步写法,减少回调地狱。下面我们从概念、语法、实现细节和实际应用四个方面深入探讨 C++20 协程。
1. 协程的基本概念
- 挂起点(Suspension Point):代码执行到
co_await、co_yield或co_return时会挂起协程。 - 恢复点(Resumption Point):协程被调用或外部事件触发后恢复执行。
- 协程句柄(
std::coroutine_handle):用于手动控制协程的生命周期、挂起和恢复。 - 协程类型:C++20 通过返回类型的特殊属性
generator、task、future等来区分不同用途的协程。
2. 协程的语法要点
2.1 基本语法
#include <coroutine>
#include <iostream>
#include <string_view>
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 unhandled_exception() { std::terminate(); }
void return_void() {}
};
std::coroutine_handle <promise_type> handle;
explicit generator(std::coroutine_handle <promise_type> h) : handle(h) {}
~generator() { if (handle) handle.destroy(); }
bool next() {
if (!handle.done()) handle.resume();
return !handle.done();
}
T value() const { return handle.promise().current_value; }
};
2.2 用法示例
generator <int> count(int start, int end) {
for (int i = start; i <= end; ++i) {
co_yield i; // 生成一个值并挂起
}
}
int main() {
for (auto g = count(1, 5); g.next(); ) {
std::cout << g.value() << ' ';
}
}
输出 1 2 3 4 5。
3. 任务协程(task)
任务协程更适合异步 I/O 或长时间运行的操作。下面演示一个简单的异步函数:
#include <coroutine>
#include <exception>
#include <iostream>
struct task {
struct promise_type {
std::exception_ptr eptr;
task get_return_object() { return task{ std::coroutine_handle <promise_type>::from_promise(*this) }; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { eptr = std::current_exception(); }
void return_void() {}
};
std::coroutine_handle <promise_type> handle;
explicit task(std::coroutine_handle <promise_type> h) : handle(h) {}
~task() { if (handle) handle.destroy(); }
void resume() { handle.resume(); }
};
task async_io_simulation() {
std::cout << "开始异步操作...\n";
co_await std::suspend_always(); // 模拟 I/O 等待
std::cout << "异步操作完成!\n";
}
int main() {
auto t = async_io_simulation();
t.resume(); // 恢复到第一暂停点
t.resume(); // 继续到最后
}
4. 与标准库协同
C++23 继续扩展协程的生态,加入 std::ranges::subrange、std::generator 等适配器。即使在 C++20,也可以配合 std::future、std::promise 或第三方库如 Boost.Asio 使用协程。
5. 性能与常见误区
| 误区 | 解释 |
|---|---|
| 协程会导致大量堆分配 | 协程帧默认放在栈上,除非使用 co_yield 或 co_await 的 awaiter 需要堆分配。 |
| 协程只能用于 I/O | 协程可以用于任何需要挂起与恢复的场景,如生成器、事件循环、状态机等。 |
| 所有协程都需要手动销毁 | 只要返回类型定义了 final_suspend 并返回 std::suspend_always,协程句柄在退出时会自动销毁。 |
6. 实战案例:异步 HTTP 请求
下面用 cpprestsdk(Casablanca)演示一个异步 HTTP GET 请求的协程实现。
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
#include <iostream>
using namespace web::http::client;
using namespace utility::conversions; // for to_string_t
task <void> fetch_and_print(const std::string& url) {
http_client client(to_string_t(url));
auto response = co_await client.request(methods::GET);
std::cout << "HTTP Status: " << response.status_code() << '\n';
auto body = co_await response.extract_string();
std::cout << "Body: " << body << '\n';
}
调用方式:
int main() {
auto t = fetch_and_print("http://www.example.com");
t.resume(); // 执行请求
}
7. 结语
C++20 的协程为语言带来了更清晰、可维护的异步编程范式。掌握协程的核心概念、语法和生命周期管理后,你可以轻松地将其应用于生成器、状态机、网络 I/O 等多种场景。随着标准库的完善(C++23 进一步提供了 std::generator 等)和社区生态的丰富,协程正逐渐成为 C++ 开发者的强大工具。
祝你在协程世界里玩得开心,写出更高效、更优雅的 C++ 代码!