【并发编程二十一:终章】c++20协程( co_yield、co_return、co_await )

2023-05-16

【并发编程二十一】c++20协程(co_yield、co_return、co_await )

  • 一、协程分类
    • 1、控制机制划分
    • 2、有栈(stackfull)/无栈(stackless)划分
  • 二、c++20协程
  • 三、co_yield
    • 1、demo
    • 2、相关知识点介绍
  • 四、co_return
  • 五、co_await

一、协程分类

上一篇我们讲解了协程实现的多种方式,但是我们没有讲解协程的分类。在此我们讲解下。

1、控制机制划分

  • 非对称协程(asymmetric coroutines):是跟一个特定的调用者绑定的,协程让出CPU时,只能让回给原调用者。
  • 对称协程(symmetric coroutines):则不同,被调协程启动之后就跟之前运行的协程没有任何关系了。

2、有栈(stackfull)/无栈(stackless)划分

有栈协程:(类似线程)每一个协程都会有自己的调用栈。(一般的协程使用栈内存来存储数据)
无栈协程:但是无栈协程不具备数据栈。

无栈协程其实现原理是将执行的方法编译为一个状态机,实现的时候不需要在临时栈和系统栈直接拷贝现场。因此无栈协程的效率和占用的资源更少。当然,有栈协程的代码会更加的简单易读。
(go语言是有栈协程——对go语言不熟悉,未验证)
(python是无栈协程——未验证)

二、c++20协程

协程就是一个可以挂起执行,稍后再恢复执行的函数。只要一个函数包含 co_await、co_yield 或 co_return 关键字,则它就是协程。

C++20 中的协程是无栈式的(stackless),协程挂起时会返回到调用者或恢复者,且恢复执行所需的数据会分开存储,而不放在栈上。

另外再说一点,我看有的文章说c++20协程是非对称协程,也就是说必须返回调用者。但是我们写代码时发现,其实也可以调用其他的,所以对网上的这些说法我持保留意见。

三、co_yield

1、demo

先写一个不能运行的c++20协程协程

MyCoroutine<int> task()
{
	int a = 0, b = 1;
	while (a <= 10)
	{
		co_yield a;
		a++;
	}
};

int main()
{
	MyCoroutine<int> fun = task();
	while (fun.MoveNext())
	{
		cout << fun.GetValue()<<endl;
			//getchar();
	}
}

之所以我们说不能运行的协程是因为,因为我们上面说了,含有上面三个关键字中的就是协程,但是协程函数返回值的类型,必须是一个自定义类型,并且这个自定义类型需要按照一定的格式来定义。比如上文中的代码MyCoroutine就是我们按照要求自定义的类型。

  • 下面我们写一个完整的挂起。并展示下如何自定义协程的返回类型。
#include <iostream>
#include<coroutine>

using namespace std;

template<typename T>
class MyCoroutine
{
public:
	// 协程开始时,在协程的状态对象分配内存后,调用promise_type的构造函数
	struct promise_type {
		T value;
		// 为协程的状态对象分配内存失败时
		static auto get_return_object_on_allocation_failure() { return MyCoroutine{ nullptr }; }

		// 构造成功后开始执行
		auto get_return_object() { return MyCoroutine{ handle::from_promise(*this) }; }
		// 在以上函数后执行
		auto initial_suspend() { return std::suspend_always{}; }
		// 协程结束前执行
		auto final_suspend() noexcept { return std::suspend_always{}; }
		// 出现未经处理的异常时执行
		void unhandled_exception() { return std::terminate();}
		// co_return 时执行,return_void跟return_value二选一
		void return_void(){}
		//int return_value(int result) { this.result = reslut; }

		//co_yield时执行
		auto yield_value(T value ) {this->value=value; return std::suspend_always{}; }

	};
	using handle = std::coroutine_handle<promise_type>;
private:
	handle hCoroutine;
	MyCoroutine(handle handle) :hCoroutine(handle) {}
public:
	//int result;
	MyCoroutine(MyCoroutine&& other)noexcept :hCoroutine(other.hCoroutine) { other.hCoroutine = nullptr; }
	~MyCoroutine() { if (hCoroutine) hCoroutine.destroy(); }
	bool MoveNext() const { return hCoroutine && (hCoroutine.resume(), !hCoroutine.done()); }
	T GetValue() const { return hCoroutine.promise().value; }
};

MyCoroutine<int> task()
{
	int a = 0, b = 1;
	while (a <= 10)
	{
		co_yield a;
		a++;
	}
};

int main()
{
	MyCoroutine<int> fun = task();
	while (fun.MoveNext())
	{
		cout << fun.GetValue()<<endl;
			//getchar();
	}
}

输出

在这里插入图片描述

2、相关知识点介绍

对比代码中的备注,我们知道

  • 协程开始时,在协程的状态对象分配内存后,调用promise_type的构造函数

    struct promise_type

  • 为协程的状态对象分配内存失败时

static auto get_return_object_on_allocation_failure() { return MyCoroutine{ nullptr }; }

  • 构造成功后开始执行

auto get_return_object() { return MyCoroutine{ handle::from_promise(*this) }; }

  • 在以上函数后执行

auto initial_suspend() { return std::suspend_always{}; }

  • 协程结束前执行

auto final_suspend() noexcept { return std::suspend_always{}; }

  • 出现未经处理的异常时执行

void unhandled_exception() { return std::terminate();}

  • co_return 时执行,return_void跟return_value二选一

void return_void(){}
//int return_value(int result) { this.result = reslut; }

  • co_yield时执行

auto yield_value(T value ) {this->value=value; return std::suspend_always{}; }

  • coroutine_handle也暴露出多个接口,用于控制协程的行为、获取协程的状态,与promise_type不同的是,promise_type里的接口需要我们填写实现,promise_type里的接口是给编译器调用的。coroutine_handle的接口不需要我们填写实现,我们可以直接调用。
coroutine_handle接口作用
from_promise()从promise对象创建一个coroutine_handle
done()检查协程是否运行完毕
operator bool检查当前句柄是否是一个coroutie
operator()恢复协程的执行
resume恢复协程的执行(同上)
destroy销毁协程
promise获取协程的promise对象
address返回coroutine_handle的指针
from_address从指针导入一个coroutine_handle

四、co_return

#include <coroutine>
#include <iostream>
#include <memory>

template<typename T>
struct MyCoroutine {
    struct promise_type;
    std::coroutine_handle<promise_type> m_handle;
    MyCoroutine(std::coroutine_handle<promise_type> handle)    // (3)
        : m_handle(handle) {
    }
    ~MyCoroutine() {
        m_handle.destroy();                              // (11)
    }
    T get() {                                          // (10)
        m_handle.resume();
        return m_handle.promise().value;
    }
    struct promise_type {
        T value = {};
        promise_type() {                              // (4)

        }
        ~promise_type() { }
        auto get_return_object() {              // (7)
            return MyCoroutine<T>{ std::coroutine_handle<promise_type>::from_promise(*this) };
        }
        void return_value(T v) {                      // (8)
            value = v;
        }
        auto initial_suspend() {          // (5)
            return std::suspend_always{};
        }
        auto final_suspend() noexcept {  // (6)
            return std::suspend_always{};
        }
        void unhandled_exception() {
            std::exit(1);
        }
    };
};

MyCoroutine<int> task() {
    co_return 2023;                                    // (9)
}

int main() {
    auto fut = task();
    std::cout << "fut.get(): " << fut.get() << '\n';   // (2)
}

输出如下

在这里插入图片描述

五、co_await

  • 以下demo是利用chatGBT写的代码基础上更改而来,(因为chatGBT写的代码编译失败)

我们先参考co_yield和co_return,写一个await的例子。(其实这里并非co_await的真正用法,还有其他的用法)

#include <iostream>
#include<coroutine>
using namespace std;

struct MyCoroutine {
        struct promise_type;
    std::coroutine_handle<promise_type> m_handle;
    MyCoroutine(std::coroutine_handle<promise_type> handle)    // (3)
        : m_handle(handle) {
    }
    ~MyCoroutine() {
        m_handle.destroy();                              // (11)
    }

    struct promise_type {
        auto get_return_object() { return MyCoroutine{ std::coroutine_handle<promise_type>::from_promise(*this) }; }
        auto initial_suspend() { return std::suspend_never{}; }
        auto final_suspend() noexcept { return std::suspend_always{}; }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };

    bool resume() {
        if (!m_handle.done())
            m_handle.resume();
        return !m_handle.done();
    }
};

MyCoroutine func() {
    std::cout << "Starting coroutine" << std::endl;
    co_await std::suspend_always{};
    std::cout << "Resuming coroutine" << std::endl;
}

int main() {
    auto gen = func();
    std::cout << "Yielding coroutine" << std::endl;
    gen.resume();
    std::cout << "Finished" << std::endl;
    return 0;
}

输出

在这里插入图片描述

至于真正的等待器的三个关键字的用法我就不写了,感兴趣的同时可以看bilibili的这个讲解。【C++20 协程(2/2)】可等待体和等待器,or稍微啰嗦的这个博客,因为太啰嗦我没仔细看细节,但是里面讲到了等待器的部分知识。所以还是推荐上面哔哩哔哩的视频up主C++20 协程(2):理解co_await运算符

后记:
【并发编程】系列到此就结束了,从2022年10月底写下第一篇【并发编程一】进程、线程、协程、芊程,到现在一步步梳理到c++20协程,最大的收获就是,之前零散的、不清晰的知识点,逐步变成了清晰的知识面。对计算机组成原理、操作系统、网络、c++相关等都有了一定的了解,收获和成长了很多,知识体系也逐步的建立起来了。

参考
1、C++20四大之二:coroutines特性详解
2、C++20 协程(一):
3、C++20协程

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【并发编程二十一:终章】c++20协程( co_yield、co_return、co_await ) 的相关文章

  • Loggernet软件新手入门(二)

    一 xff0e 创建一个程序 在loggernet软件中 xff0c 创建一个程序有两种方式 xff1a Short Cut以及CRBasic Editor xff0c 这两种方式都可以在Program目录下找到 1 1Short Cut
  • PC200W-简版loggernet软件

    一 xff0e 解压缩PC200W文件夹 xff0c 双击下图图标 xff0c 可以打开PC200W 二 xff0e 创建一个连接 打开软件后 xff0c 会弹出一个关于创建连接的对话框 xff0c 如果没有弹出 xff0c 可以在Netw
  • 示例一:CR300接CS655

    Public PTemp Batt volt Public CS655 3 Alias CS655 1 61 vwc Alias CS655 2 61 ec Alias CS655 3 61 T Units vwc 61 Units ec
  • 日本原装COM 3600F专业型空气负离子检测仪 --CR1000采集

    Public PTemp Batt volt Public instring string As String 36 Public Num spilt 3 As String 3 Alias Num spilt 1 61 ION FM Pu
  • java FlowLayout示例

    java FlowLayout示例 xff1a import java awt FlowLayout import javax swing JLabel import javax swing WindowConstants import j
  • Get 一个显示界面,与数采串口通信

    程序第一步 xff1a 显示 数据来源 xff0c CR1000数据采集器 xff0c 5秒采集并存储上传 第二步 xff1a 存储 TXT文档存储 xff0c 逗号分隔 xff0c 每月创建一个新的文件 xff0c 可以另存为excel文
  • UART通信协议

    UART通信协议 一 UART是什么 xff1f 1 同步串口通信 vs 异步串口通信2 串行通信 二 通信协议三 工作原理四 特点 一 UART是什么 xff1f 通用异步收发传输器 xff08 Universal Asynchronou
  • win10右下角的通知区域

    属性 gt 通知和操作 gt 选择在任务栏上显示哪些图标 gt
  • UART一对多通信的方法

    通常 xff0c uart为单对单通信 xff0c 当用到一对多时可以用RS485 然而有时候我们MCU的uart口只剩一个 xff0c 又要接多个uart的外围芯片 xff0c 这时如果转成RS485需要加多个485收发器 xff0c 成
  • 全网最全的 postman 工具使用教程

    正文如下 xff0c 如果觉得有用欢迎点赞 关注 postman是一款支持http协议的接口调试与测试工具 xff0c 其主要特点就是功能强大 xff0c 使用简单且易用性好 无论是开发人员进行接口调试 xff0c 还是测试人员做接口测试
  • 星际争霸1终于可以在win10上运行了

    win7的时候 xff0c 星际争霸1就不能运行 xff0c 只好装了个虚拟机 xff0c 在虚拟机里玩 刚刚更新到了win10 xff0c 总觉得在虚拟机里玩不是个事 xff0c 就去网上搜索 xff0c 终于发现了办法 在 StarCr
  • windows下编译opencv 3.4.0

    为了方便后期的调试 xff0c 自己动手编译opencv3 4 0 xff0c 这样有需要的时候还可以自己修改修改源代码 通常来说 xff0c 编译32位比较简单 xff0c 直接用cmake生成编译的工程就行了 xff0c 但64位就比较
  • opencv添加的新接口clearVec()的实现

    自己编译的opencv xff0c 之前文章有说添加了这个接口 xff0c 也有上传3 3 0版本添加这个接口之后编译好的库 xff0c 但是没有把实现过程展现出来 xff0c 导致有些朋友问我如何实现的 xff0c 今天把这个实现放出来
  • 苏泊尔电饭煲不工作的维修

    本篇文章与其说是维修 xff0c 倒不如说成是 拆 xff0c 因为维修相对容易 xff0c 但想拆开却很艰难 xff0c 大部分的时间都花在了拆的工作上面 老家伙的样子如下 型号为 xff1a CYSB50FC99 100 xff0c 铭
  • 萨克斯吹不响的解决办法

    刚开始吹萨克斯 xff0c 发现总是吹不响 看各种入门的文章 xff0c 很多都强调口型的重要性 xff0c 各文章说得也都差不多 xff0c 我仔细捉摸 xff0c 不断尝试 xff0c 似乎还是不得要领 特别是安装好之后 xff0c 很
  • vs2010制作安装工程

    这里的安装工程 xff0c 是指制作安装包 xff0c 而不是vs2010的安装包 用向导生成一个安装工程 xff0c 通常会直接打开一个文件编辑窗口 xff1a 这个窗口很容易编辑 xff0c 把所有要安装的文件拖到 应用程序文件夹 上
  • windows下编译ffmpeg源代码

    由于工作原因 xff0c 需要使用ffmpeg在windows下进行代码跟踪 于是 xff0c 上网找相关文章 xff0c 搜索出来有很多 xff0c 经过查看 xff0c 其中的一个英文网站是最好的 xff0c 网址 xff1a http
  • 注册控件失败之一:提示0x80040200错误的处理办法

    今天有客户反馈说控件无法注册 xff0c 晕 xff0c 这问题好容易困扰开发者以及客服人员 xff0c 但是环境千差万别 xff0c 很难做到完全自动化 出现的错误号码有很多 xff0c 但相对的0x80040200这个号码出现的概率较其
  • win10+ubuntu23.04双系统安装

    win10 win10先安装好 xff08 确保主板上各个螺丝稳定 xff0c 至少4对螺丝 43 铜柱 xff0c 否则会各种蓝屏 xff09 如果双系统安装失败了 xff0c 连win10都进不去了 xff0c 用原版ISO刻录的U盘或
  • 冷门指标移中平均线和多空指数的完美结合(一定要看)

    注 xff0c 原贴地址 xff1a http blog sina com cn s blog 7f0a6fa50101hyls html 在此谨以记录防止原帖无法打开为忧 冷门指标移中平均线和多空指数的完美结合 一定要看 xff09 20

随机推荐