C++ 20 协程总结

C++ 20 协程总结

介绍

C++ 20提供的是非对称的、一等对象、无栈的协程(Coroutines in C++20 are asymmetric, first-class, and stackless)

所谓协程,即用户级线程,一种用于将异步代码同步化的编程机制,使得程序的执行流可以在多个并行事务之间切换但又不必承担切换带来的过高的性能损耗。当前很多的编程语言都内置协程特性或者有自己的协程库,如C/C++的libco、golang的goroutine等。而在实现机制上,又可以划分为有栈协程和无栈协程

协程是可以在保持状态的同时暂停和恢复执行的函数

image-20220925140527159

非对称协程与对称协程

非对称协程(asymmetric coroutines)是跟一个特定的调用者绑定的,协程让出CPU时,只能让回给原调用者。那到底是什么东西“不对称”呢?第一,非对称在于程序控制流转移到被调协程时使用的是suspend/resume操作,而当被调协程让出 CPU 时使用的却是return/yield操作。第二,协程间的地位也不对等,caller与callee关系是确定的,不可更改的,非对称协程只能返回最初调用它的协程。微信团队的libco其实就是一种非对称协程,Boost C++库也提供了非对称协程。另外,挂起(suspend)和恢复(resume)跟yield的区别是:yield后的协程,之后还会被切换回来,但是被suspend挂起的协程,除非调用resume()恢复它,否则永远不会再被执行到。在不同语言中,这三者会有不同的叫法,比如call也会调用新函数时也会同时实现suspend旧函数的功能,有的语言用yield/resume和return,不一而论,但区别不变。

对称协程(symmetric coroutines)则不同,被调协程启动之后就跟之前运行的协程没有任何关系了。协程的切换操作,一般而言只有一个操作yield或return,用于将程序控制流转移给另外的协程。对称协程机制一般需要一个调度器的支持,按一定调度算法去选择yield或return的目标协程。Go语言提供的协程,其实就是典型的对称协程。不但对称,goroutines还可以在多个线程上迁移。这种协程跟操作系统中的线程非常相似,甚至可以叫做“用户级线程”。

一等对象(第一类对象)

[python - What are “first-class” objects? - Stack Overflow](https://stackoverflow.com/questions/245192/what-are-first-class-objects

第一类对象(英语:First-class object)在计算机科学中指可以在执行期创造并作为参数传递给其他函数或存入一个变量的实体[1]。将一个实体变为第一类对象的过程叫做“物件化”(Reification)[2]。

无栈协程

有栈(stackful)协程通常的实现手段是在堆上提前分配一块较大的内存空间(比如 64K),也就是协程所谓的“栈”,参数、return address 等都可以存放在这个“栈”空间上。如果需要协程切换,那么通过 swapcontext 一类的形式来让系统认为这个堆上空间就是普通的栈,这就实现了上下文的切换。

有栈协程最大的优势就是侵入性小,使用起来非常简便,已有的业务代码几乎不需要做什么修改,但是 C++20 最终还是选择了使用无栈协程,主要出于下面这几个方面的考虑。

无栈协程是一个可以暂停和恢复的函数,是函数调用的泛化。

我们知道一个函数的函数体(function body)是顺序执行的,执行完之后将结果返回给调用者,我们没办法挂起它并稍后恢复它,只能等待它结束。而无栈协程则允许我们把函数挂起,然后在任意需要的时刻去恢复并执行函数体,相比普通函数,协程的函数体可以挂起并在任意时刻恢复执行。

img

所以,从这个角度来说,无栈协程是普通函数的泛化。

总结一下,有栈协程是用户态线程,无栈协程就是函数调用

设计目标

  • 高度可伸缩性
  • 高效的恢复和挂起函数操作
  • 与已有设施无缝衔接,没有开销
  • 允许开发者设计协程库,开放高级语义的接口
  • 在禁用异常的环境可以使用

成为协程

一个函数成为一个协程,通过使用以下关键字中的一个

  • co_return
  • co_await
  • co_yield
  • 循环中的co_awaitimage-20220926150955803

区分协程工厂和协程对象

术语协程通常用于协程的两个方面:一个是调用了co_awaitco_yieldco_return的函数,另一个是协程对象

使用一个协程术语形容协程的两个方面会让人糊涂

MyFuture<int> createFuture() {
co_return 2021; }
int main() {
auto fut = createFuture();
std::cout << "fut.get(): " << fut.get() << '\n'; }

函数createFuture是一个协程工厂返回一个协程对象。协程对象时一个可恢复对象,使用协程框架来指定他的行为

协程框架

实现协程的框架包含了20多个函数,一些必须实现,一些必须重写,因此你可以定制协程的功能

一个协程与三个部分相关:

  • promise object
  • coroutine handle
  • coroutine frame

通过coroutine handle协程句柄与promise object进行交互,并将上下文保存在coroutine frame

image-20220926145654224

编译器在协程执行过程中会自动调用这些函数

协程句柄(coroutine handle

协程句柄是一个非拥有的句柄,用于从外部恢复或销毁协程帧(frame)。协程句柄是可恢复函数的一部分。

template <typename T>
struct Generator
{struct promise_type;using handle_type = std::coroutine_handle<promise_type>;Generator(handle_type h): coro(h){}handle_type coro;~Generator(){if (coro) coro.destroy();}T getValue(){return coro.promise().current_value;}bool next(){coro.resume();return not coro.done();}
}
  • 恢复协程执行:coro.resume()
  • 销毁协程:coro.destroy()
  • 检查状态:coro(15行)

协程帧Coroutine Frame

协程帧维持着协程堆内存的分配状态,包含promise_type,协程复制的参数,挂起点的表示,局部变量等

  • 协程的生命周期必须嵌套在调用者的生命周期内
  • 协程的调用者知到协程帧的大小

协程帧的关键是可等待体(**Awaitables **),等待器(Awaiters

可等待体和等待器

promise_type中的三个函数返回可等待体 yield_value, initial_suspend, final_suspend

可等待体

可等待体决定协程是否暂停

本质上,编译器使用promise和co_await操作符生成这三个函数调用。

image-20220928161637063

co_await需要一个可等待体作为参数

实现可等待体需要三个函数

image-20220928161810250

C++20标准已经定义了两个基本的对象:std::suspend_alwaysstd::suspend_never

The Awaitable std::suspend_always

struct suspend_always {
constexpr bool await_ready() const noexcept { return false; }
constexpr void await_suspend(std::coroutine_handle<>) const noexcept {}
constexpr void await_resume() const noexcept {}
};

总是挂起,await_ready返回false

The Awaitable std::suspend_never

struct suspend_never {
constexpr bool await_ready() const noexcept { return true; }
constexpr void await_suspend(std::coroutine_handle<>) const noexcept {}
constexpr void await_resume() const noexcept {}
};

从不挂起,await_ready返回true

当协程协程执行的时候,这两个函数会自动执行:

  • 开始 initial_suspend
  • 结束 final_suspend

initial_suspend

当initial_suspend返回suspend_always时,协程会在开始时挂起;返回suspend_never时,则不会挂起

A lazy coroutine

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

A eager coroutine

std::suspend_never initial_suspend() {
return {};
}

final_suspend

在协程结束时执行,与几乎initial_suspend相同

可等待体和等待器

  • 可被等待的对象称为可等待(awaitable )体或者表达式;
  • co_await运算符必须返回一个等待器(awaiter):
    • 可等待体和等待器可以是同一个类型;
    • std::future(实验)是可等待体。
    • co_await运算符返回等待器_Future_awaiter

工作流

00000000000000000000000000000000000

编译器执行两个工作流外部的promise工作流和内部的awaiter工作流

Promise 工作流

当在函数中使用co_yield, co_await, co_return,函数成为一个协程,并且编译器将其转换成等价的如下代码

The transformed coroutine

image-20220928165209696

主要工作步骤:

  • 协程开始执行:
    • 申请必要的协程帧空间
    • 拷贝所有函数参数到协程帧
    • 创建promise_type对象
    • 调用promise_type中的get_return_object方法创建协程句柄(coroutine handle),并保持在局部变量中,当协程第一次挂起时,将返回给调用者
    • 调用initial_suspend并且co_await其结果,可能返回suspend_always/never
    • co_await prom.initial_suspend恢复resume时,函数体执行
  • 协程到达挂起点:
    • 返回对象(prom.get_return_object())将返回给恢复协程的调用程序
  • 协程到达co_return
    • 调用prom.retrun_void/value没有返回值或者返回值
    • 销毁变量
    • 调用prom.final_suspend()并且co_await它的结果
  • 协程销毁
    • 调用promise_type对象和函数参数对象的析构函数
    • 释放协程帧的内存
    • 返还控制权给调用者
  • 调用co_await执行等待器工作流

Awaiter工作流

调用co_await会让编译器执行三个函数:await_ready await_suspend await_resume

The generated Awaiter Workflow

image-20220928171135751

image-20220928171146726

只有await_ready返回false时,流程才会执行,否则的话直接返回await_resume的结果

await_ready返回false时:

  • 协程挂起,计算awaitable.await_suspend()的返回值,返回值有很多种情况

image-20220928171651423

出现异常情况不想写了

co_return

协程使用co_return作为返回语句

template <typename T>
struct MyFuture
{std::shared_ptr<T> value;MyFuture(std::shared_ptr<T> p): value(p){}~MyFuture(){}T get(){return *value;}struct promise_type{std::shared_ptr<T> ptr = std::make_shared<T>();~promise_type(){}MyFuture<T> get_return_object() { return ptr; }void return_value(T v) { *ptr = v; }std::suspend_never initial_suspend() { return {}; }std::suspend_never final_suspend() noexcept { return {}; }void unhandled_exception(){std::terminate();}};
};MyFuture<int> createFuture()
{co_return 2021;
}int main(int argc, char* argv[])
{auto fut = createFuture();std::cout << fut.get() << std::endl;
}

image-20220928202106636

  • 流程
    • 初始化协程
      • 申请必要的协程帧空间
      • 拷贝所有函数参数到协程帧
      • 创建promise_type对象
      • 调用promise_type中的get_return_object方法将ptr传给fut
    • 调用co_return
      • 调用return_value并传入参数2022
    • 输出fut.get()

co_yield

无限数据流

template <typename T>
struct Generator
{struct promise_type;using handle_type = std::coroutine_handle<promise_type>;Generator(handle_type h): coro(h){}handle_type coro;~Generator() { if (coro) coro.destroy(); }Generator(const Generator&) = delete;Generator& operator =(const Generator&) = delete;Generator(Generator&& oth) noexcept : coro(oth.coro){oth.coro = nullptr;}Generator& operator =(Generator&& oth) noexcept{coro = oth.coro;oth.coro = nullptr;return *this;}T getValue(){return coro.promise().current_value;}bool next(){coro.resume();return !coro.done();}struct promise_type{promise_type() = default;~promise_type() = default;auto initial_suspend(){return std::suspend_always{};}auto final_suspend() noexcept{return std::suspend_always{};}auto get_return_object(){return Generator{handle_type::from_promise(*this)};}auto return_void(){return std::suspend_never{};}auto yield_value(const T value){current_value = value;return std::suspend_always{};}void unhandled_exception(){std::terminate();}T current_value;};
};Generator<int> getNext(int start = 0, int step = 1)
{auto value = start;while (true){co_yield value;value += step;}
}int main(int argc, char* argv[])
{auto gen = getNext();for (int i = 0; i <= 10; ++i){gen.next();std::cout << std::format("gen value: {}\n", gen.getValue());}std::cout << "\n\n";auto gen2 = getNext(100, -10);for (int i = 0; i <= 20; ++i){gen2.next();std::cout << std::format("gen2 value: {}\n", gen2.getValue());}
}

image-20220928214526409

看一下执行流程;

  • 创建promise_type
  • 调用get_return_object(),将其结果保存在局部变量
  • 创建generator
  • 调用initial_suspend挂起协程
  • 请求下一个值并消耗一个值然后挂起
  • 接着调用gen.next重复循环

co_await

struct Job
{struct promise_type;using handle_type = std::coroutine_handle<promise_type>;handle_type coro;Job(handle_type h): coro(h){}~Job(){if (coro) coro.destroy();}void start(){coro.resume();}struct promise_type{auto get_return_object(){return Job{handle_type::from_promise(*this)};}std::suspend_always initial_suspend(){std::cout << "准备工作\n";return {};}std::suspend_always final_suspend() noexcept{std::cout << "执行工作\n";return {};}void return_void(){}void unhandled_exception(){}};
};Job prepareJob()
{co_await std::suspend_never();
}int main(int argc, char* argv[])
{std::cout << "工作之前\n";auto job = prepareJob();job.start();std::cout << "工作之后\n";
}

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

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

相关文章

VC++6.0开发环境之快捷键

F1&#xff1a; 帮助 CtrlO &#xff1a;Open CtrlP &#xff1a;Print CtrlN &#xff1a;New CtrlShiftF2 &#xff1a;清除所有书签 F2 &#xff1a;上一个书签 ShiftF2 &#xff1a;上一个书签 AltF2 &#xff1a;编辑书签 Ctrl&#xff0b;F2 &#xff1a;添加/删除一个书…

react(92)--批量删除进行置空操作

//批量删除handleDeleteAll () > {this.props.dispatch({type: startpage/updateState,payload: { selectedRowKeys: [] },});const { selectedRowKeys } this.props.startpage;if (selectedRowKeys.length 0) {message.error(当前未选择);return false;}this.deletePoin…

.net程序员的盲点(八):泛型

1.泛型介绍泛型类和泛型方法同时具备可重用性、类型安全和效率&#xff0c;这是非泛型类和非泛型方法无法具备的。泛型通常用在集合和在集合上运行的方法中。.NET Framework 2.0 版类库提供一个新的命名空间System.Collections.Generic&#xff0c;其中包含几个新的基于泛型的集…

C++20 协程实现线程同步示例

C20 协程实现线程同步示例 class Event { public:Event() default;Event(const Event&) delete;Event(Event&&) delete;Event& operator(const Event&) delete;Event& operator(Event&&) delete;class Awaiter;Awaiter operator co_await…

react(93)--成功置空

//删除页面逻辑的封装deletePointsDefinition (list) > {this.props.dispatch({type: activity/deleteActivityPopup,payload: {codeList: list,},callback: (res) > {console.log(res, res);if (res.returnCode 0) {message.success(操作成功);\this.props.dispatch({…

给窗口设置系统级或窗口级的热键

开发环境&#xff1a;VS2005, C#语言 为了给一个程序加一个系统级的热键&#xff0c;在开发时需要使用到下面的代码&#xff0c;就是在程序中注册和卸载热键。 1。首先在Form1类的函数中添加如下代码&#xff1a; [DllImport("user32.dll")] private static…

Sublime Text 插件之常用20个插件

作为一个开发者你不可能没听说过 Sublime Text。不过你没听说过也没关系&#xff0c;下面让你明白。 Sublime Text是一款非常精巧的文本编辑器&#xff0c;适合编写代码、做笔记、写文章。它用户界面十分整洁&#xff0c;功能非同凡响&#xff0c;性能快得出奇。这些非常棒的特…

JUnit 4 与 JUnit 3

JUnit 是 Java? 语言事实上的 标准单元测试库。JUnit 4 是该库三年以来最具里程碑意义的一次发布。它的新特性主要是通过采用 Java 5 中的标记&#xff08;annotation&#xff09;而不是利用子类、反射或命名机制来识别测试&#xff0c;从而简化测试。在本文中&#xff0c;执着…

输出特殊形状的图形

输出如下面这种形状的图形&#xff1a; 4 3 7 2 6 9 1 5 8 10 C Code: #include<iostream> using namespace std; //N: 代表有多少行元素void Display(int N){ int i, j, v; if(N < 0) { cout<<"N must bigger than Zero(0)!"<<endl<&l…

整合quickx到普通cocos2dx

quickx是对cocos2dx的lua扩展&#xff0c;它做了一些C的扩展&#xff0c;同时还在lua做了一些封装&#xff0c; 让用lua开发cocos2dx更快&#xff0c;中文站http://quick.cocoachina.com/。 由于现在的项目对cocos2dx有一些修改&#xff0c;又想用到quickx的便捷&#xff0c;于…

react(94)--时间搜索传值规范

console.log(data, dataTime);let startTimeLong (data?.time && new Date(data?.time[0]).getTime()) || ;let endTimeLong (data?.time && new Date(data?.time[1]).getTime()) || ;this.setState({name: data?.name,startTimeLong,endTimeLong,pageI…

我的项目-财务系统

4 名称&#xff1a;财务管理系统 时间&#xff1a;2000 用时&#xff1a;3个月 vb6sqlserver7 独立完成 描述&#xff1a;包含凭证输入&#xff0c;审核&#xff0c;记帐&#xff0c;帐簿管理&#xff0c;自动转帐&#xff0c;会计报表等财务管理的整个流程。此项目在兖州…

对二维数组进行Zig-Zag扫描(C++)

对二维数组进行Zig-Zag扫描(C)&#xff0c;先自定义了一个类&#xff0c;类中有个函数Run()来实现这个扫描过程&#xff0c;二维数组是动态分配空间以及随机赋值的。 下图是Zig-Zag扫描方式&#xff1a; CZigZag.h: #include<iostream>using namespace std; typedef s…

Storing and Retrieving Images from SQL Server using Microsoft .NET

Storing and Retrieving Images from SQL Server using Microsoft .NET 原文 Storing and Retrieving Images from SQL Server using Microsoft .NET Download source - 19.6 KbIntroduction This article is about storing and retrieving images from database in Microsoft …

react(95)--外部定义变量

export const oneTab [{ name: 所有, code: },{ name: 草稿, code: 15973725291430011977912 },{ name: 已上架, code: 15973725579180011047799 },{ name: 已下架, code: 15973725862620011529122 }, ];

无钱生活的日子

无钱生活的日子——代腾飞 2007年9月5日 于成都无钱生活的日子心中的梦想俨然成为了一句空话无钱生活的日子每日计划着怎样不受饥饿的摧残而饱受尴尬无钱生活的日子就算是兄弟朋友也会给你奚落笑话无钱生活的日子我甚至不能尽孝回乡看我重病在床的妈心中只能增添一份惆怅的牵…

react(96)--switch做判断

list: (text, row) > {const list [];switch (row.status) {case COURSE_STATUS_UPSHEFF:list.push({ name: 查看详情, onClick: this.handleDetail });list.push({ name: 下架, onClick: this.handleUporDownsheff });break;case COURSE_STATUS_DOWNSHEFF:list.push({ nam…

flot绘制折线图

<!--请先确保你有jquery.js 和 jquery.flot.min.js--> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns"http://www.w3.org/1999/xhtm…

在dos下运行.exe程序(C++)

说明&#xff1a;在Dos下运行.exe程序(C) 先看C源文件&#xff1a; #include<iostream>using namespace std; void main(int argc, char * argv[]){ cout<<"argc "<<argc<<endl; for(int i 0; i < argc; i) cout<<argv[i]<…

react(97)--分支切换

# 1.查看所有分支 > git branch -a# 2.查看当前使用分支(结果列表中前面标*号的表示当前使用分支) > git branch# 3.切换分支 > git checkout 分支名