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

news2025/4/7 9:13:45

【并发编程二十一】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协程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/389687.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何让AI帮你干活-娱乐(2)

背景&#xff1a;好容易完成朋友的任务&#xff0c;帮忙给小朋友绘画比赛生成一些创意参考图片。他给我个挑战更高的问题&#xff0c;是否可以帮他用AI生成一些视频。这个乍一听以现在AI技术根本不太可能完成。奈何他各种坚持&#xff0c;无奈被迫营业。苦脸接受了这个不可能完…

Java线程知识点总结

文章目录Java 线程基础线程简介什么是进程什么是线程进程和线程的区别创建线程ThreadRunnableCallable、Future、FutureTaskCallableFutureFutureTaskCallable Future FutureTask 示例线程基本用法线程休眠线程礼让终止线程守护线程线程通信wait/notify/notifyAlljoin管道线程…

MATLAB——数据及其运算

MATLAB数值数据数值数据类型的分类1&#xff0e;整型整型数据是不带小数的数&#xff0c;有带符号整数和无符号整数之分。表中列出了各种整型数据的取值范围和对应的转换函数。2&#xff0e;浮点型浮点型数据有单精度(single&#xff09;和双精度&#xff08;(double)之分&…

精粤X99M-PLUS D3+ E5-2696 v3电脑 Hackintosh 黑苹果efi引导文件

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。硬件型号驱动情况主板精粤X99M-PLUS D3处理器E5-2696 v3已驱动内存64GB ECC DDR3 1866MHz (16GB*4)已驱动硬盘TOPMORE CAPRICORNUS NVMe 1TB已驱动显卡AMD Radeon™ RX 570 series (4GB/MSI)已驱动声卡Realtek ALC897 英特…

Android framework系列2 - Init进程

1、源码 入口&#xff1a;system/core/init/main.cpp2 流程图 https://note.youdao.com/s/EtnCswft 3、代码详解 主入口共三步&#xff0c;如流程图所示&#xff0c;我们主要看下最后一步 入口在init.cpp下&#xff0c;这个阶段主要来解析init.rc并执行此文件下的命令 看到…

多人协作|RecyclerView列表模块新架构设计

多人协作|RecyclerView列表模块新架构设计多人协作设计图新架构设计与实现设计背景与新需求新架构设计多人协作设计图 根据产品设计&#xff0c;将首页列表即将展示内容区域&#xff0c;以模块划分成多个。令团队开发成员分别承接不同模块进行开发&#xff0c;且互不影响任务开…

【Maven】P2 创建 Maven java/web 工程

Maven项目Maven 项目构建命令使用 Maven插件 创建 java/web 工程创建工程格式创建 java 工程创建 web 工程IDEA 中创建 Maven Java 工程IDEA 中创建 Maven web 工程Maven 项目构建命令 mvn compile # 编译 mvn clean # 清理 mvn test # 测试 mvn package # 打包 mvn …

0626-0631韩顺平Java Buffered字节处理流 学习笔记

如何去构建字节流package com.hspedu.outputstream_;import java.io.*;/*** author abner* version 1.0*/ public class BufferedCopy02 {public static void main(String[] args) {String srcFilePath "D:\\Users\\Pictures\\Camera Roll\\Pierre-Auguste_Renoir,_Le_Mo…

java基本数据类型变量间的运算规则

基本数据类型变量间的运算规则。 运算规则包括&#xff1a; 这里提到可以做运算的基本数据类型有7种&#xff0c;不包含boolean类型 1.自动类型提升 2.强制类型转换 自动类型提升日规则&#xff1a;当容量小的变量与容量大的变量做运算时&#xff0c;结果自动转换为容量大的数…

mvn命令

在IDEA右侧Maven菜单中&#xff0c;有以下几种指令。 clean&#xff1a;清理&#xff0c;清除上一次构建生产的文件。执行该命令会删除项目地址下的target文件&#xff0c;但不会删除本地的maven已生成的文件。 validate&#xff1a;验证&#xff0c;验证项目是否正确且所有必…

「史上最全的 TCG 规范解读」TCG 规范架构概述(下)

可信计算组织&#xff08;Ttrusted Computing Group,TCG&#xff09;是一个非盈利的工业标准组织&#xff0c;它的宗旨是加强不同计算机平台上计算环境的安全性。TCG 于 2003 年春成立&#xff0c;并采纳了由可信计算平台联盟&#xff08;the Trusted Computing Platform Allia…

【Linux】P4 Linux 权限 chmod chown

Linux 权限认知权限信息chmod 修改权限chown 修改用户与用户组认知权限信息 序号1&#xff1a;文件、文件夹权限控制信息&#xff1b; 权限控制信息一共有十位 第 1 位&#xff1a; - 表示文件&#xff0c;d 表示文件夹&#xff0c;l 表示软链接 第 2~4 位&#xff1a; 所属用…

JDK19下载、安装与测试的完整图文教程

一、下载JDK 1、官网获取&#xff1a;https://www.oracle.com/ 1.1 点击“Products”&#xff1b; 1.2 选择“Java”&#xff1b; 1.3 选择“Download Java”&#xff1b; 1.4 选择“Java downloads”&#xff0c;这里以最新版&#xff08;JDK19&#xff09;为例&#xff…

Python基础—文件操作(二)

Python基础—文件操作(二) CSV格式文件 逗号分隔值&#xff0c;以纯文本形式存储表格数据 由任意数目的记录组成&#xff0c;记录间以换行符分隔 每条记录由字段组成&#xff0c;字段间用逗号或制表符分隔 每条记录都有同样的字段序列 如有列名&#xff0c;位于文件第一行 每条…

【编程实践】代码之中有创意:“我一直认为工程师世界上最具创造性的工作之一”

代码之中有创意 “我一直认为工程师世界上最具创造性的工作之一”。 文章目录 代码之中有创意一、代码可以赋予创造力1.1 代码的创造力1.2 如何发挥代码的创造力二、有创意的代码可以提高工作效率2.1 代码创意可以提高工作效率2.2 如何利用代码创意来提高工作效率三、代码创意可…

【壹】嵌入式系统硬件基础

随手拍拍&#x1f481;‍♂️&#x1f4f7; 日期: 2023.2.28 地点: 杭州 介绍: 日子像旋转毒马&#x1f40e;&#xff0c;在脑海里转不停&#x1f92f; &#x1f332;&#x1f332;&#x1f332;&#x1f332;&#x1f332; 往期回顾 &#x1f332;&#x1f332;&#x1f332…

【Java 类】001-访问修饰符、命名规范

【Java 类】001-访问修饰符、命名规范 文章目录【Java 类】001-访问修饰符、命名规范一、访问修饰符概述1、是什么2、作用作用问题3、访问修饰符有哪些4、作用对象二、访问修饰符使用演示1、类访问修饰符演示第一步&#xff1a;创建 Dog 类&#xff1a;public第二步&#xff1a…

画图说透 ZooKeeper如何保证数据一致性:选举和ZAB协议

1、zookeeper是什么&#xff1f; zookeeper能被各个牛逼的中间件项目中所依赖&#xff0c;已经说明了他的地位。一出手就是稳定的杀招。zookeeper是什么&#xff1f;官网中所说&#xff0c;zookeeper致力于开发和维护成为一个高度可靠的分布式协调器。 开局一张图&#xff0c;…

STM32MP157-Linux音频应用编程-简易语音助手

文章目录前言STM32MP157简易语音助手alsa-lib简介&#xff1a;移植alsa-lib库&#xff1a;libcurl库简介&#xff1a;移植libcurl库&#xff1a;API调用修改asrmain.c文件修改token.c文件录音文件IO打开音频文件硬件控制sysfs文件系统数据解析和控制多线程主循环实现效果及注意…

Hive与HBase的区别及应用场景

当数据量达到一定量级的时候&#xff0c;存储和统计计算查询都会遇到问题&#xff0c;今天了解一下Hive和Hbase的区别和应用场景。 一、定义 Hive是基于Hadoop的一个数据仓库工具&#xff0c;可以将结构化的数据文件映射为一张数据库表&#xff0c;并提供简单的sql查询功能&am…