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()函数自动生成的,而非我们主动声明的,这和我们之前的使用方式,差异很大。

复制代码
相关推荐
云和数据.ChenGuang17 分钟前
Ascend C 核心技术特性
c语言·开发语言
kyle~3 小时前
C++---value_type 解决泛型编程中的类型信息获取问题
java·开发语言·c++
NiNi_suanfa6 小时前
【Qt】Qt 批量修改同类对象
开发语言·c++·qt
小糖学代码6 小时前
LLM系列:1.python入门:3.布尔型对象
linux·开发语言·python
Data_agent6 小时前
1688获得1688店铺详情API,python请求示例
开发语言·爬虫·python
信奥胡老师7 小时前
苹果电脑(mac系统)安装vscode与配置c++环境,并可以使用万能头文件全流程
c++·ide·vscode·macos·编辑器
妖灵翎幺7 小时前
C++ 中的 :: 操作符详解(一切情况)
开发语言·c++·ide
Halo_tjn7 小时前
虚拟机相关实验概述
java·开发语言·windows·计算机
star _chen7 小时前
C++实现完美洗牌算法
开发语言·c++·算法
周杰伦fans7 小时前
pycharm之gitignore设置
开发语言·python·pycharm