C++协程入门
co_routine协程入门自学协程笔记,旨在用自己能接受的简单的语言一步一步详细的认知协程,了解运转流程。学知有限,内容可能有误,欢迎指出。
更专业的请参考BennyHuo的视频和博客。
什么是协程
普通函数:像一次性说完一整段话,中间不能停,说完了就结束。
协程:像打电话时“你等一下,我查个资料,别挂”,查完继续聊。可以暂停,稍后恢复,能多次这样操作。
下面给出协程所需的结构类型大概浏览,不需要你现在知道什么意思。
struct Task {
struct promise_type {
// 创建协程时调用
Task get_return_object() {
return Task{std::coroutine_handle<promise_type>::from_promise(*this)};
}
// 初始挂起点(true=立即暂停,false=立即执行)
std::suspend_always initial_suspend() { return {}; }
// 最终挂起点(true=暂停,false=不暂停)
struct promise_type {
Task get_return_object();
std::suspend_always initial_suspend();
std::suspend_always final_suspend();
void unhandled_exception();
};
// 处理 co_return 的值
void return_void() {}
// 处理异常
Task get_return_object() {
return Task{std::coroutine_handle<promise_type>::from_promise(*this)};
}
};
std::coroutine_handle<promise_type> handle;
Task(std::coroutine_handle<promise_type> h) : handle(h) {}
~Task() { if (handle) handle.destroy(); }
// 恢复协程
void resume() {
if (handle && !handle.done()) {
handle.resume();
}
}
};
/*
我们这里折叠简化一下
*/
struct Task {
struct promise_type {
Task get_return_object();
std::suspend_always initial_suspend();
std::suspend_always final_suspend();
void return_void() ;
void unhandled_exception();
};
std::coroutine_handle<promise_type> handle;
...
};经过折叠之后的结构很清晰,我们可以看到外围的Task结构体和内部的promise_type结构体。外部的结构体可以随意取名,内部的promise_type是C++语法要求。
当然我们也可以分开写,随便定义内部结构体的名字,只需要Task使用别名promise_type代替那个结构体。如下:
struct 随便取一个名字 {
Task get_return_object();
std::suspend_always initial_suspend();
std::suspend_always final_suspend();
void return_void() ;
void unhandled_exception();
};
struct Task {
// 一定要内部有promise_type
using promise_type = 随便取一个名字;
std::coroutine_handle<promise_type> handle;
...
};上述结构只是协程需要的一个返回类型,但并不是协程。真正的协程函数如下:
Task myCoroutine() {
std::cout << "协程开始\n";
co_await std::suspend_always{};// 暂停
std::cout << "协程恢复\n";
co_await std::suspend_always{};// 再暂停
std::cout << "协程结束\n";
}
[*]final_suspend
struct promise_type {
Task get_return_object();
std::suspend_always initial_suspend();
std::suspend_always final_suspend();
void unhandled_exception();
};这个函数是在整个协程结束之后调用的,同样也返回一个等待体,用于告诉协程结束后是否需要挂起。一般我们使用std::suspend_always,表示结束后总是挂起。挂起的好处是可以让外部的Task去进行手动的清理资源,防止悬空引用。否则可以想象,我们外部的Task还持有协程的句柄,但是协程结束后直接清理资源了,那我们Task手里的handle不就是悬空的吗,如果使用直接出错。
[*]unhandled_exception
Task get_return_object() {
return Task{std::coroutine_handle<promise_type>::from_promise(*this)};
}这个函数用于处理异常。我们可以选择不同的方式进行处理。这里我们使用了直接结束程序。当然我们也可以在我们的promise_type内部创建一个变量,然后unhandled_exception用于将异常保存在这个变量上,也是一种常见的做法。示意:
// 编译器生成的伪代码
Task my_coroutine() {
// 1. 分配协程帧(包含 promise 对象)
coroutine_frame* frame = operator new(sizeof(coroutine_frame));
// 2. 构造 promise 对象(在协程帧内)
new (&frame->promise) Task::promise_type();
// 3. 获取协程帧的句柄(这里才是句柄的"来源")
// 句柄本质上是指向 frame 的指针包装
auto handle = std::coroutine_handle<Task::promise_type>::from_address(frame);
// 4. 调用 get_return_object(),传入 handle
// 注意:from_promise(*this) 只是从 promise 反推回 handle
Task return_object = frame->promise.get_return_object();
return return_object;
}
协程帧 (堆内存)
↓
promise 对象 (在协程帧内)
↓
from_promise(*this) 获取 handle
↓
get_return_object() 返回 Task
↓
Task 构造函数接收 handle
↓
task 对象存储 handle初次尝试协程
ok啊,讲完了基本的概念,我们可以先尝试一下协程的效果。我们用AI写了一个简单的例子:
#include #include #include // 1. 定义返回类型(包含 promise_type)struct Task { struct promise_type { Task get_return_object() { return Task{std::coroutine_handle::from_promise(*this)}; } std::suspend_always initial_suspend() { return {}; } struct promise_type {
Task get_return_object();
std::suspend_always initial_suspend();
std::suspend_always final_suspend();
void unhandled_exception();
}; Task get_return_object() {
return Task{std::coroutine_handle<promise_type>::from_promise(*this)};
} }; std::coroutine_handle handle; Task(std::coroutine_handle h) : handle(h) {} ~Task() { if (handle) handle.destroy(); }};Task myCoroutine() { std::cout
页:
[1]