什么是C ++ 20中的协同程序?

时间:2017-04-19 18:39:10

标签: c++ coroutine c++20

中的协程是什么?

它与“Parallelism2”或/和“Concurrency2”的不同之处(见下图)?

以下图片来自ISOCPP。

https://isocpp.org/files/img/wg21-timeline-2017-03.png

enter image description here

3 个答案:

答案 0 :(得分:123)

在一个抽象的层面上,Coroutines将执行状态与执行线程的想法分开了。

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

线程有多个“执行线程”和多个执行状态。您有多个程序和多个执行线程。

Coroutines具有多个执行状态,但不拥有执行线程。你有一个程序,程序有状态,但它没有执行的线程。

最简单的协程示例是来自其他语言的生成器或枚举。

在伪代码中:

function Generator() {
  for (i = 0 to 100)
    produce i
}

调用Generator,第一次调用它时返回0。它的状态被记住(状态随协程的实现而变化),下次你调用它时会继续它停止的地方。所以它下次返回1。然后2。

最后它到达循环的末尾并从函数的末尾掉落;协程已经完成。 (这里发生的事情因我们所讨论的语言而异;在python中,它会引发异常)。

协同程序将此功能带入C ++。

有两种协同程序;堆叠和无堆叠。

无堆栈协程只在其状态及其执行位置存储局部变量。

堆栈协程存储整个堆栈(如线程)。

无拼接协程可以非常轻量级。我读到的最后一个提议基本上将你的功能改写成有点像lambda的东西;所有局部变量都进入对象的状态,标签用于跳转到/从协同程序“产生”中间结果的位置。

生成值的过程称为“yield”,因为协同程序有点像协作多线程;你正在把执行点交还给来电者。

Boost有一个堆栈协程的实现;它可以让你调用一个函数来为你屈服。堆叠的协程更强大,但也更昂贵。

协同程序比简单的发电机更多。您可以在协程中等待协程,这样您就可以以有用的方式编写协同程序。

协同程序,如if,循环和函数调用,是另一种“结构化goto”,它允许您以更自然的方式表达某些有用的模式(如状态机)。

C ++中Coroutines的具体实现有点有趣。

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

一个函数通过在其体内使用其中一个来成为一个协程。因此,根据他们的声明,他们与功能无法区分。

当在函数体中使用这三个关键字中的一个时,会发生一些标准强制检查返回类型和参数,并将该函数转换为协程。此检查告诉编译器在函数挂起时存储函数状态的位置。

最简单的协程是一个生成器:

generator<int> get_integers( int start=0, int step=1 ) {
  for (int current=start; 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_line可能是异步协同程序,它们打开文件并从中读取行。 co_await将暂停和就绪状态load_data连接到他们的进度。

C ++协同程序比这更灵活,因为它们是在用户空间类型之上实现的最小语言功能集。用户空间类型有效地定义了co_return co_awaitco_yield 意味着什么 - 我见过人们使用它来实现monadic可选表达式,例如{ {1}}在空的可选项上自动将空状态传播到外部可选:

co_await

而不是

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

答案 1 :(得分:9)

协程就像一个C函数,它有多个return语句,当第二次调用时,它不会在函数开始时执行,而是在上一次执行返回后的第一条指令处开始执行。此执行位置与所有自动变量一起保存,这些变量将存在于非协程函数的堆栈中。

Microsoft之前的一个实验性协程实现确实使用了复制堆栈,因此您甚至可以从深层嵌套函数返回。但这个版本被C ++委员会拒绝了。例如,您可以使用Boosts光纤库来实现此实现。

答案 2 :(得分:1)

协同程序应该是(在C ++中)能够“等待”某些其他例程完成的函数,并提供暂停,暂停,等待,例程继续执行所需的任何操作。 C ++人员最感兴趣的功能是协同程序理想情况下不会占用堆栈空间...... C#已经可以通过await和yield来做类似的事情了,但是可能需要重建C ++来实现它。

并发性主要集中在关注点的分离上,其中关注点是程序应该完成的任务。这种关注点的分离可以通过多种方式实现......通常是某种形式的授权。并发的概念是,许多进程可以独立运行(关注点分离),而“监听器”会将这些分离的问题产生的任何内容引导到它应该去的任何地方。这在很大程度上取决于某种异步管理。有许多并发方法,包括面向方面编程和其他方法。 C#有'委托'运算符,效果非常好。

并行性听起来像并发并且可能涉及但实际上是一个物理构造,涉及许多处理器以或多或少的并行方式排列,软件能够将代码的一部分引导到将运行的不同处理器,结果将同步收到。