供挂 发表于 昨天 18:40

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]
查看完整版本: C++协程入门