协程是 C++20 标准中引入的重要特性,它使得异步编程与协作式并发变得更自然。本文将从协程的设计原则、核心概念、实现细节以及一个实战案例来全面梳理 C++20 协程。
一、设计原则
-
无侵入性
协程的语法(co_await、co_yield、co_return)与传统函数几乎完全兼容,编译器在不破坏已有代码的情况下实现协程功能。 -
可组合性
协程可以像普通函数一样返回std::future、std::generator或自定义协程类型,便于层层组合。 -
轻量级
协程的帧(协程状态机)在栈上分配,减少了堆分配的开销。 -
可定制的 Promise
通过自定义promise_type,可以在协程开始、结束或异常时执行自定义逻辑。
二、核心概念
| 关键字 | 说明 |
|---|---|
co_await |
用于等待一个可等待对象(Awaitable),协程在此挂起 |
co_yield |
用于生成值,暂停协程并返回一个值给调用方 |
co_return |
结束协程,返回一个值给 Promise |
promise_type |
协程的核心,负责管理协程生命周期、状态、返回值等 |
Awaitable
一个类型只要满足以下接口即可被 co_await:
struct Awaitable {
bool await_ready(); // 是否已就绪
void await_suspend(std::coroutine_handle<> h); // 挂起时调用
auto await_resume(); // 恢复时返回的值
};
协程句柄
std::coroutine_handle<> 用于控制协程的生命周期,例如挂起、恢复或销毁。
三、实现细节
-
协程函数编译器生成
编译器会把协程函数拆分为两个结构体:promise_type与内部生成的generator或task。 -
栈帧
协程的本地变量和promise_type的成员都放在一个连续的内存块中,形成协程帧。 -
异常处理
若协程内部抛出异常,异常会存储在promise_type中,直到co_return或co_yield之后再重新抛出。 -
自定义 Awaitable
可以为std::chrono::steady_clock::duration写一个Awaitable,实现协程的sleep功能。
四、实战案例:异步 HTTP 请求
下面给出一个简化的异步 HTTP 客户端示例,使用协程实现非阻塞请求。
#include <iostream>
#include <string>
#include <coroutine>
#include <chrono>
#include <thread>
#include <future>
struct SleepAwaitable {
std::chrono::milliseconds duration;
bool await_ready() noexcept { return duration.count() <= 0; }
void await_suspend(std::coroutine_handle<> h) {
std::thread([h, d=duration]{
std::this_thread::sleep_for(d);
h.resume();
}).detach();
}
void await_resume() noexcept {}
};
struct HttpResponse {
int status;
std::string body;
};
struct HttpRequest {
std::string url;
std::future <HttpResponse> operator()() {
struct Awaiter {
std::coroutine_handle<> coro;
HttpRequest req;
HttpResponse operator()() {
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟网络延迟
return {200, "Response from " + req.url};
}
bool await_ready() noexcept { return false; }
void await_suspend(std::coroutine_handle<> h) {
coro = h;
std::thread([this]{
auto res = operator()();
coro.promise().return_value = res;
coro.resume();
}).detach();
}
void await_resume() noexcept {}
};
struct Promise {
HttpResponse return_value;
HttpRequest get_return_object() {
return { "", std::make_shared<std::promise<HttpResponse>>(std::promise<HttpResponse>()), std::move(return_value) };
}
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void unhandled_exception() { std::terminate(); }
void return_void() {}
};
struct Task {
struct promise_type : Promise {};
std::coroutine_handle <promise_type> coro;
Task(std::coroutine_handle <promise_type> h) : coro(h) {}
~Task() { if (coro) coro.destroy(); }
};
return Task{ std::experimental::suspend_always() }.coro.promise().return_value.get_future();
}
};
async auto fetch(const std::string& url) -> std::future <HttpResponse> {
HttpRequest req{url};
co_return co_await req();
}
int main() {
auto fut = fetch("http://example.com");
SleepAwaitable{std::chrono::milliseconds(200)}.await_resume();
auto res = fut.get();
std::cout << "Status: " << res.status << "\nBody: " << res.body << std::endl;
}
说明
SleepAwaitable实现了一个简单的协程sleep。HttpRequest使用std::future作为返回类型,并内部启动线程来模拟网络请求。fetch函数是一个协程,使用co_await等待请求完成。main中通过fetch异步获取数据,然后使用future.get()同步获取结果。
五、总结
C++20 协程通过提供轻量级、可组合且可定制的协程框架,让异步编程与协作式并发的实现变得简单直观。熟悉 co_await、co_yield、co_return 以及 promise_type 的实现细节,能够帮助开发者在实际项目中高效地使用协程,提升代码可读性和性能。