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

Pav*_*aka 84 c++ coroutine c++20

什么是中的协同程序?

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

以下图片来自ISOCPP.

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

在此输入图像描述

Yak*_*ont 155

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

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

线程有多个"执行线程"和多个执行状态.您有多个程序和多个执行线程.

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


协同程序最简单的例子是来自其他语言的生成器或枚举.

在伪代码中:

function Generator() {
  for (i = 0 to 100)
    produce i
}
Run Code Online (Sandbox Code Playgroud)

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; true; current+= step)
    co_yield current;
}
Run Code Online (Sandbox Code Playgroud)

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) );
}
Run Code Online (Sandbox Code Playgroud)

load_data是一个协程,它std::future在命令资源打开时生成一个,我们设法解析到我们找到所请求数据的点.

open_resourceread_lines可能是异步协同程序,它们打开文件并从中读取行.该co_await连接的悬挂和准备状态load_data,以他们的进步.

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

modified_optional<int> add( modified_optional<int> a, modified_optional<int> b ) {
  return (co_await a) + (co_await b);
}
Run Code Online (Sandbox Code Playgroud)

代替

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;
}
Run Code Online (Sandbox Code Playgroud)

  • 这是我读过的关于协同程序的最清楚的解释之一.将它们与SIMD和经典线程进行比较是一个很好的主意. (16认同)
  • 我不明白 add-options 示例。std::optional&lt;int&gt; 不是可等待的对象。 (3认同)

Lot*_*har 13

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

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

  • 为什么它“像一个C函数”而不是“像一个函数”? (2认同)