What is a coroutine?

什么是协程

随着coroutine ts正式进入c++20,c++已经进入协程时代了。c++20提供的无栈协程,拥有许多无与伦比的优越性,比如说没有传染性,可以与以前非协程风格的代码并存,再比如说不需要额外的调度器,总之是个好东西,我们首先需要知道,什么是协程?

“协程”一词由两个词组成: “co” (cooperative ) 和 “routines” (functions)。协程是协作的功能。

From Wikipedia

协程是计算机程序的是概括组件子程序用于非抢先多任务被暂停和重新开始时,通过允许执行。协程非常适合于实现熟悉的程序组件,例如协作任务,异常,事件循环,迭代器,无限列表和管道。

From Coroutines (C++20)

协程是可以暂停执行以在以后恢复的功能。协程没有堆栈:它们通过返回到调用方来挂起执行,并且恢复执行所需的数据与堆栈分开存储。这允许异步执行的顺序代码(例如,在没有显式回调的情况下处理非阻塞I / O),并且还支持延迟计算的无限序列和其他用途的算法。

  • SIMD(单指令多个数据)具有多个“执行线程”,但只有一个执行状态(它仅适用于多个数据)。可以说并行算法有点像这样,因为您有一个在不同数据上运行的“程序”。

  • 线程具有多个“执行线程”和多个执行状态。您拥有多个程序和一个以上执行线程。

  • 协程具有多个执行状态,但不拥有执行线程。您有一个程序,并且该程序具有状态,但是没有执行线程。


为什么需要协程

通常,当一个函数调用第二个函数时,第一个函数无法继续,直到第二个函数完成并返回到其调用位置为止。

该控件将保留第二个功能,直到它完全执行,然后才可以返回第一个功能。

在这里插入图片描述
函数调用的每次响应中,都有一个初始化的堆栈来跟踪其所有变量和常量,并在函数调用时被销毁结束。

void FirstFunction(){
	SecondFunction();
}
void SecondFunction()
{
}

协程是将控件从一个function转移到另一个function的功能,这样可以记住第一个function的退出点和第二个function的入口点,而无需更改上下文

因此,每个函数都记住它离开执行的位置以及应该从何处恢复。
在这里插入图片描述
A()调用B()时,控件将转移到B(),直到将转移交给B(),直到给出语句为止。 这样,两个function都可以协调。

协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断。比如子程序A、B:

void A()
{
    cout << "1" << endl;
    cout << "2" << endl;
    cout << "3" << endl;
}


void B()
{
    cout << "x" << endl;
    cout << "y" << endl;
    cout << "z" << endl;
}

假设由协程执行,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,结果可能是:

1
2
x
y
3
z

但是在A中是没有调用B的,所以协程的调用比函数调用理解起来要难一些。

使用此概念,您可以在执行过程中在函数之间传递值,而不会破坏每个函数的上下文。

在这里插入图片描述
协程的特点在于是一个线程执行,协程并不是“线程”,它并不会参与 CPU 时间调度,并没有均衡分配到时间。


协程与线程

那和多线程比,协程有何优势?

  1. 最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

  2. 第二大优势就是不需要多线程的锁机制。线程的话,在多核心处理器里面,是并行的(在运行单元层面,线程在同一个运行单元的同一时间也只能跑一个任务,他只是能定时切换所看起来是一起跑的),你启动一个线程之后你要想控制它,你得做系统调用 thread_cancel 或者最好情况是发送信号告诉线程里面的条件变量让线程自己去退出。

    因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

  3. 另一个好处是内存使用率低得多。使用线程模型,每个线程都需要分配自己的堆栈,因此您的内存使用量随您拥有的线程数呈线性增长。使用协同例程,您拥有的例程数量与内存使用量没有直接关系

Coroutines are a form of sequential processing: only one is executing at any given time (just like subroutines AKA procedures AKA functions – they just pass the baton among each other more fluidly). Threads are (at least conceptually) a form of concurrent processing: multiple threads may be executing at any given time. (Traditionally, on single-CPU, single-core machines, that concurrency was simulated with some help from the OS – nowadays, since so many machines are multi-CPU and/or multi-core, threads will de facto be executing simultaneously, not just “conceptually”).

两个协程并不是线程,它们根本【既不并发也不并行】的,协程实际上是一个很普通的函数(对于 C 语言理解),或者一个代码块(ObjC),或者子过程(Pasacal Perl),反正就是只是中间加了 yield,让它跑了一半暂停执行,然后产出结果给调度它(这个协程)的父级上下文,如果父级不再需要执行下去了可以先调用别的函数,等别的 yield 了再 transfer 回去执行这个。因此:协程就是一串比函数粒度还要小的可手动控制的过程。

因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。


c++20协程

在最基本的层次上,它向C++添加了一些关键字co_return co_await co_yield,以及一些与它们一起使用的库类型。

通过在体内具有一种功能,该功能就可以成为协程。因此,从它们的声明中它们与函数没有区别。

当在函数体中使用这三个关键字之一时,将对返回类型和参数进行一些标准的强制检查,并将函数转换为协程。该检查告诉编译器在功能暂停时将功能状态存储在何处。

限制条件

  1. 协程不能使用可变参数,普通返回语句或占位符返回类型(auto或Concept)。
  2. Constexpr函数,构造函数,析构函数和main函数不能为协程。

最简单的协程是 generator生成器:

generator<int> get_integers( int start=0, int step=1 ) {
  for (int current=start; true; current+= step)
    co_yield current;
}

co_yield暂停函数执行,将该状态存储在中generator<int>,然后current通过返回值generator<int>

您可以循环返回的整数。

co_await同时让您将一个协程拼接到另一个。如果您在一个协程中,并且在进行之前需要等待的结果(通常是协程)的结果,那么您co_await就可以了。如果它们准备好了,请立即进行;如果没有,您将暂停直到等待的等待就绪。

std::future<std::expected<std::string>> load_data( std::string resource )
{
  auto handle = co_await open_resouce(resource);
  while( auto line = co_await read_line(handle)) {
    if (std::optional<std::string> r = parse_data_from_line( line ))
       co_return *r;
  }
  co_return std::unexpected( resource_lacks_data(resource) );
}

load_data是一个协程,当打开指定的资源时,它会生成std :: future,并且我们设法解析到找到所请求数据的位置。

open_resourceread_lines可能是异步协程,它们会打开文件并从中读取行。co_awaitload_dataSuspendingReady状态连接到其Progress

C ++协程比这更加灵活,因为它们是作为用户空间类型之上的最少语言功能集实现的。 用户空间类型有效地定义了co_return co_awaitco_yield的含义。我已经看到人们使用它来实现monadic optional expressions,这样,在空的可选项上的co_await自动将空状态传播到外部可选项:

modified_optional<int> add( modified_optional<int> a, modified_optional<int> b ) {
  return (co_await a) + (co_await b);
}

代替

std::optional<int> add( std::optional<int> a, std::optional<int> b ) {
  if (!a) return std::nullopt;
  if (!b) return std::nullopt;
  return *a + *b;
}


参考资料:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值