找回密码
 立即注册
首页 业界区 业界 C++协程入门

C++协程入门

供挂 3 小时前
co_routine协程入门

自学协程笔记,旨在用自己能接受的简单的语言一步一步详细的认知协程,了解运转流程。学知有限,内容可能有误,欢迎指出。
更专业的请参考BennyHuo的视频和博客。
什么是协程

普通函数:像一次性说完一整段话,中间不能停,说完了就结束。
协程:像打电话时“你等一下,我查个资料,别挂”,查完继续聊。可以暂停,稍后恢复,能多次这样操作。
下面给出协程所需的结构类型大概浏览,不需要你现在知道什么意思。
  1. struct Task {
  2.     struct promise_type {
  3.         // 创建协程时调用
  4.         Task get_return_object() {
  5.             return Task{std::coroutine_handle<promise_type>::from_promise(*this)};
  6.         }
  7.         
  8.         // 初始挂起点(true=立即暂停,false=立即执行)
  9.         std::suspend_always initial_suspend() { return {}; }
  10.         
  11.         // 最终挂起点(true=暂停,false=不暂停)
  12.         struct promise_type {
  13.     Task get_return_object();
  14.     std::suspend_always initial_suspend();
  15.     std::suspend_always final_suspend();
  16.     void unhandled_exception();
  17. };
  18.         
  19.         // 处理 co_return 的值
  20.         void return_void() {}
  21.         
  22.         // 处理异常
  23.         Task get_return_object() {
  24.     return Task{std::coroutine_handle<promise_type>::from_promise(*this)};
  25. }
  26.     };
  27.    
  28.     std::coroutine_handle<promise_type> handle;
  29.    
  30.     Task(std::coroutine_handle<promise_type> h) : handle(h) {}
  31.     ~Task() { if (handle) handle.destroy(); }
  32.    
  33.     // 恢复协程
  34.     void resume() {
  35.         if (handle && !handle.done()) {
  36.             handle.resume();
  37.         }
  38.     }
  39. };
  40. /*
  41.         我们这里折叠简化一下
  42. */
  43. struct Task {
  44.     struct promise_type {
  45.         Task get_return_object();
  46.         std::suspend_always initial_suspend();
  47.         std::suspend_always final_suspend();
  48.         void return_void() ;
  49.         void unhandled_exception();
  50.     };
  51.     std::coroutine_handle<promise_type> handle;
  52.         ...
  53. };
复制代码
经过折叠之后的结构很清晰,我们可以看到外围的Task结构体和内部的promise_type结构体。外部的结构体可以随意取名,内部的promise_type是C++语法要求。
当然我们也可以分开写,随便定义内部结构体的名字,只需要Task使用别名promise_type代替那个结构体。如下:
  1. struct 随便取一个名字 {
  2.         Task get_return_object();
  3.         std::suspend_always initial_suspend();
  4.         std::suspend_always final_suspend();
  5.         void return_void() ;
  6.         void unhandled_exception();
  7. };
  8. struct Task {
  9.     // 一定要内部有promise_type
  10.     using promise_type = 随便取一个名字;
  11.     std::coroutine_handle<promise_type> handle;
  12.         ...
  13. };
复制代码
上述结构只是协程需要的一个返回类型,但并不是协程。真正的协程函数如下:
  1. Task myCoroutine() {
  2.     std::cout << "协程开始\n";
  3.     co_await std::suspend_always{};  // 暂停
  4.     std::cout << "协程恢复\n";
  5.     co_await std::suspend_always{};  // 再暂停
  6.     std::cout << "协程结束\n";
  7. }
复制代码

  • final_suspend
  1. struct promise_type {
  2.     Task get_return_object();
  3.     std::suspend_always initial_suspend();
  4.     std::suspend_always final_suspend();
  5.     void unhandled_exception();
  6. };
复制代码
这个函数是在整个协程结束之后调用的,同样也返回一个等待体,用于告诉协程结束后是否需要挂起。一般我们使用std::suspend_always,表示结束后总是挂起。挂起的好处是可以让外部的Task去进行手动的清理资源,防止悬空引用。否则可以想象,我们外部的Task还持有协程的句柄,但是协程结束后直接清理资源了,那我们Task手里的handle不就是悬空的吗,如果使用直接出错。

  • unhandled_exception
  1. Task get_return_object() {
  2.     return Task{std::coroutine_handle<promise_type>::from_promise(*this)};
  3. }
复制代码
这个函数用于处理异常。我们可以选择不同的方式进行处理。这里我们使用了直接结束程序。当然我们也可以在我们的promise_type内部创建一个变量,然后unhandled_exception用于将异常保存在这个变量上,也是一种常见的做法。示意:
  1. // 编译器生成的伪代码
  2. Task my_coroutine() {
  3.     // 1. 分配协程帧(包含 promise 对象)
  4.     coroutine_frame* frame = operator new(sizeof(coroutine_frame));
  5.    
  6.     // 2. 构造 promise 对象(在协程帧内)
  7.     new (&frame->promise) Task::promise_type();
  8.    
  9.     // 3. 获取协程帧的句柄(这里才是句柄的"来源")
  10.     // 句柄本质上是指向 frame 的指针包装
  11.     auto handle = std::coroutine_handle<Task::promise_type>::from_address(frame);
  12.    
  13.     // 4. 调用 get_return_object(),传入 handle
  14.     // 注意:from_promise(*this) 只是从 promise 反推回 handle
  15.     Task return_object = frame->promise.get_return_object();
  16.    
  17.     return return_object;
  18. }
  19. 协程帧 (堆内存)
  20.     ↓
  21. promise 对象 (在协程帧内)
  22.     ↓
  23. from_promise(*this) 获取 handle
  24.     ↓
  25. get_return_object() 返回 Task
  26.     ↓
  27. Task 构造函数接收 handle
  28.     ↓
  29. task 对象存储 handle
复制代码
初次尝试协程

ok啊,讲完了基本的概念,我们可以先尝试一下协程的效果。我们用AI写了一个简单的例子:
[code]#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

相关推荐

您需要登录后才可以回帖 登录 | 立即注册