C++进阶:coroutine 协程

1. 总述

协程的本质是一个可以暂停和恢复执行的函数,只要函数体出现了【co_await】、【co_yield】、【co_return】这三个关键字之一,则这个函数将自动变成协程。

而C++20引入的协程是一个无栈协程,其内存分配(协程帧)时机是在协程首次挂起时,注意不是函数调用时,内存默认在堆上分配,是否需要手动释放,依赖于co_return的返回值,详细内容会在下章节介绍。协程帧一般包括如下内容:

bash 复制代码
┌─────────────────┐
│  协程帧头部     │ ← 编译器内部信息
├─────────────────┤
│  promise对象    │
├─────────────────┤
│  参数副本       │ ← 函数参数按值/引用存储
├─────────────────┤
│  局部变量       │ ← 跨挂起点的局部变量
├─────────────────┤
│  挂起点信息     │ ← 恢复位置、寄存器保存等
├─────────────────┤
│  激活记录       │ ← 嵌套调用信息(如适用)
└─────────────────┘

C++中对协程函数的返回值类型做了限制,返回值类型必须要包含一个名称为【promise_type】的内嵌类型,其主要用于控制协程的行为。

相对于线程,C++协程的优势有很多,主要包括:

  1. 用同步风格写异步代码,代码可读性更好
  2. 协程创建更快,内存占用更低(不涉及栈空间分配),而且因为仅仅是用户层的调度,避免了线程上下文切换开销,性能也更优

2. 协程关键字/函数说明

C++协程中用到了多种关键字,这里面先做一个简单介绍,后续结合代码再做进一步理解:

1. co_await

该关键字的作用是将协程函数异步挂起,等待条件满足后再继续执行,语法为:

cpp 复制代码
auto result = co_await expr; //expr必须为Awaitable类型对象

Awaitable类型至少要包含如下三个成员函数,以便于协程自动化调用,co_wait关键字执行时,会立刻调用await_ready()函数,并根据其结果,进一步确认是否需要调用await_suspend().

当通过协程句柄(std::coroutine_handle<>)调用其resume()函数时,Awaitable类的await_resume()将被调用。

cpp 复制代码
struct MyAwaitable {
    // 1. 检查是否需要挂起,
    // true:无需挂起,同普通函数,false:自动调用await_suspend函数
    bool await_ready();
    
    // 2. 挂起时的处理逻辑
    void await_suspend(std::coroutine_handle<> handle);
    
    // 3. 恢复时获取结果
    auto await_resume();
};

2. co_yield

co_yield 是 C++ 协程中用于生成值序列并挂起协程 的关键字。它将协程转变为生成器(Generator),能够按需产生值序列,在每次产生值后自动挂起,等待下一次请求。且若要使用该关键字,则pomise_type类中必须包含yield_value函数的实现。

使用语法为:

cpp 复制代码
// promise_type函数必须实现yield_value()
co_yield expr;

3. co_return

co_return是 C++ 协程中用于结束协程执行并返回最终结果的关键字。它标志着协程的完成,负责清理资源、设置返回值,并将协程置于最终状态。

co_return关键字执行时,会自动化调用promise_type类型中的return_value()或return_void()函数,以获取最终的结果。并在其执行完毕后,自动化执行promise_type对象的final_suspend()函数。

常见语法为:

cpp 复制代码
// 返回一个值
co_return expression;

// 无返回值(void 协程)
co_return;

4. std::suspend_never

主要用于修饰如下三个函数,表征该函数执行完成后无需挂起,要立刻继续执行:

  • initial_suspend():协程立即开始执行,不初始挂起
  • final_suspend():协程完成后自动销毁
  • yield_value():co_yield 后不挂起,继续执行

5. std::suspend_always

std::suspend_always 是一个总是挂起的 Awaitable 类型,它告诉协程必须挂起,等待后续恢复

  • initial_suspend():协程创建后立即挂起,等待手动恢复
  • final_suspend():协程完成后保持挂起,内存需要手动清理
  • yield_value():co_yield 后挂起,等待下一次请求

3. 示例代码及流程解析

下面将结合代码介绍下,协程的整体流程,代码如下:

cpp 复制代码
class IntReader {
public:
   bool await_ready() {
     return false;
   }
   void await_suspend (std::coroutine_handle<> h) {
     std::thread([this, h]() {
     sleep(1000);
     value_ = 1;
     h.resume();
    })
     thread.detach();
   }
   await_resume() {
     return value_;
   }
private:
  int value_{0};
};

class Task {
  class pomise_type{
   public:
    pomise_type() : value_(std::make_shared<int>()){}
    
    Task get_return_object() {
        return Task{std::coroutine_handle<promise_type>::from_promise(*this),value_};
    }

    void return_value (int value) {
        *value_ = value;
    }

    std::suspend_never initial_suspend() {  return {};}
    std::suspend_never final_suspend() noexcept { return {}; }
    void unhandled_exception(){}
   private:
     std::shared_ptr<int> value_;
  };  
public:
    Task(std::coroutine_handle<promise_type> handle,const std::shared_ptr<int> & value) : handle_(handle),value_(value) {}
    int GetValue() const {
        return *value;
    }
private:
    std::shared_ptr<int> value_;
    std::coroutine_handle<promise_type> handle_;
};


Task GetInt() {
    IntReader reader1;
    int total = co_wait reader1;

    IntReader reader2;
    total += co_wait reader2;
    
    IntReader reader3;
    total += co_wait reader3;

    co_return total;
}

int main() {
    auto task = GetInt();
    std::cout << task.GetValue() << std::endl;
    return 0;
}

首先,这里需要强调的是,在调用协程函数时,函数的返回值,即Task对象是有系统自动化调用promise_type对象的get_return_object()函数自动生成的,而非我们主动声明的,这和我们之前的使用方式,差异很大。

复制代码
相关推荐
Emilin Amy3 分钟前
【C++】【STL算法】那些STL算法替代的循环
开发语言·c++·算法·ros1/2
遇印记8 分钟前
蓝桥java求最大公约数
java·开发语言
ONExiaobaijs9 分钟前
【无标题】
java·开发语言·spring·maven·程序员创富
IMPYLH14 分钟前
Lua 的 String(字符串) 模块
开发语言·笔记·单元测试·lua
符哥200818 分钟前
Mybatis和Mybatis-plus区别
java·开发语言·mybatis
天赐学c语言21 分钟前
1.16 - 二叉树的中序遍历 && 动态多态的实现原理
数据结构·c++·算法·leecode
企业对冲系统官24 分钟前
期货与期权一体化平台风险收益评估方法与模型实现
运维·服务器·开发语言·数据库·python·自动化
fpcc25 分钟前
跟我学C++中级篇—std::is_swappable手动实现
c++
卜锦元30 分钟前
EchoChat搭建自己的音视频会议系统01-准备工作
c++·golang·uni-app·node.js·音视频
ceclar12334 分钟前
C++使用numeric
开发语言·c++