C++20 中的协程是什么?
What are coroutines in C++20?
c++20 中的协程是什么?
它与 "Parallelism2" or/and "Concurrency2" 有什么不同(看下图)?
下图来自ISOCPP
https://isocpp.org/files/img/wg21-timeline-2017-03.png
协程应该是(在 C++ 中)能够 "wait" 让其他例程完成并提供挂起、暂停、等待、例程继续所需的任何东西的函数。 C++ 人员最感兴趣的特性是协程理想情况下不占用堆栈 space...C# 已经可以使用 await 和 yield 做类似的事情,但 C++ 可能必须重新构建才能将其放入。
并发主要关注关注点分离,其中关注点是程序应该完成的任务。这种关注点分离可以通过多种方式实现……通常是某种委托。并发的想法是,许多进程可以 运行 独立(关注点分离),并且 'listener' 会将这些分离的关注点产生的任何内容引导到它应该去的任何地方。这在很大程度上依赖于某种异步管理。有许多实现并发的方法,包括面向方面的编程和其他方法。 C# 有 'delegate' 运算符,效果很好。
并行听起来像并发并且可能涉及但实际上是一种物理结构,涉及许多处理器,这些处理器以或多或少并行的方式排列,软件能够将部分代码定向到不同的处理器运行 并同步返回结果。
在抽象层面上,协程将具有执行状态的想法与具有执行线程的想法分开。
SIMD(单指令多数据)有多个“执行线程”但只有一个执行状态(它只适用于多个数据)。可以说并行算法有点像这样,因为你有一个针对不同数据的“程序”运行。
线程有多个“执行线程”和多个执行状态。你有不止一个程序,不止一个执行线程。
协程有多种执行状态,但不拥有一个执行线程。你有一个程序,程序有状态,但它没有执行线程。
协程最简单的例子是来自其他语言的生成器或枚举。
在伪代码中:
function Generator() {
for (i = 0 to 100)
produce i
}
调用了Generator
,第一次调用是returns 0
。它的状态会被记住(有多少状态随着协程的实现而变化),下次你调用它时它会从它停止的地方继续。所以下次它returns 1。然后2.
终于到达循环的结尾,掉出函数的结尾;协程完成了。 (这里发生的事情因我们所谈论的语言而异;在 python 中,它会抛出异常)。
协同程序为 C++ 带来了这种能力。
协程有两种;堆叠和无堆叠。
无堆栈协程仅在其状态和执行位置中存储局部变量。
stackful 协程存储整个堆栈(如线程)。
Stackless 协程可以非常轻量级。我读到的最后一个提案涉及基本上将您的函数重写成有点像 lambda 的东西;所有局部变量都进入对象的状态,标签用于跳转 to/from 协程“产生”中间结果的位置。
产生一个值的过程称为“yield”,因为协程有点像协作式多线程;您正在将执行点返回给调用者。
Boost 有一个堆栈协程的实现;它可以让你调用一个函数来为你屈服。 Stackful 协程更强大,但也更昂贵。
协程不仅仅是一个简单的生成器。您可以在协程中等待协程,这让您可以以有用的方式组合协程。
协同程序,如 if、循环和函数调用,是另一种“结构化 goto”,可让您以更自然的方式表达某些有用的模式(如状态机)。
Coroutines在C++中的具体实现有点意思
在最基本的层面上,它向 C++ 添加了一些关键字:co_return
co_await
co_yield
,以及一些与它们一起工作的库类型。
一个函数通过在其主体中包含一个协程而成为一个协程。所以从他们的声明来看,他们与函数没有区别。
当在函数体中使用这三个关键字之一时,会发生一些标准强制检查 return 类型和参数,并且函数会转换为协程。此检查告诉编译器在函数挂起时将函数状态存储在何处。
最简单的协程是生成器:
generator<int> get_integers( int start=0, int step=1 ) {
for (int current=start; true; current+= step)
co_yield current;
}
co_yield
暂停函数执行,将该状态存储在 generator<int>
中,然后 return 通过 generator<int>
存储 current
的值。
你可以遍历整数 returned.
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_resource
和 read_line
s 可能是打开文件并从中读取行的异步协程。 co_await
将 load_data
的挂起和就绪状态与其进度联系起来。
C++ 协程比这灵活得多,因为它们是在 user-space 类型之上实现的最小语言功能集。 user-space 类型有效地定义了 co_return
co_await
和 co_yield
mean ——我见过人们用它来实现monadic 可选表达式使得空可选上的 co_await
自动将空状态传播到外部可选:
modified_optional<int> add( modified_optional<int> a, modified_optional<int> b ) {
co_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;
}
协程就像一个 C 函数,它有多个 return 语句,当第二次调用时,它不会在函数的开头开始执行,而是在前一个执行后的第一条指令处开始执行 return.此执行位置与非协程函数中将存在于堆栈中的所有自动变量一起保存。
Microsoft 之前的实验性协程实现确实使用了复制堆栈,因此您甚至可以 return 来自深层嵌套函数。但是这个版本被 C++ 委员会否决了。例如,您可以使用 Boosts 纤维库获得此实现。
c++20 中的协程是什么?
它与 "Parallelism2" or/and "Concurrency2" 有什么不同(看下图)?
下图来自ISOCPP
https://isocpp.org/files/img/wg21-timeline-2017-03.png
协程应该是(在 C++ 中)能够 "wait" 让其他例程完成并提供挂起、暂停、等待、例程继续所需的任何东西的函数。 C++ 人员最感兴趣的特性是协程理想情况下不占用堆栈 space...C# 已经可以使用 await 和 yield 做类似的事情,但 C++ 可能必须重新构建才能将其放入。
并发主要关注关注点分离,其中关注点是程序应该完成的任务。这种关注点分离可以通过多种方式实现……通常是某种委托。并发的想法是,许多进程可以 运行 独立(关注点分离),并且 'listener' 会将这些分离的关注点产生的任何内容引导到它应该去的任何地方。这在很大程度上依赖于某种异步管理。有许多实现并发的方法,包括面向方面的编程和其他方法。 C# 有 'delegate' 运算符,效果很好。
并行听起来像并发并且可能涉及但实际上是一种物理结构,涉及许多处理器,这些处理器以或多或少并行的方式排列,软件能够将部分代码定向到不同的处理器运行 并同步返回结果。
在抽象层面上,协程将具有执行状态的想法与具有执行线程的想法分开。
SIMD(单指令多数据)有多个“执行线程”但只有一个执行状态(它只适用于多个数据)。可以说并行算法有点像这样,因为你有一个针对不同数据的“程序”运行。
线程有多个“执行线程”和多个执行状态。你有不止一个程序,不止一个执行线程。
协程有多种执行状态,但不拥有一个执行线程。你有一个程序,程序有状态,但它没有执行线程。
协程最简单的例子是来自其他语言的生成器或枚举。
在伪代码中:
function Generator() {
for (i = 0 to 100)
produce i
}
调用了Generator
,第一次调用是returns 0
。它的状态会被记住(有多少状态随着协程的实现而变化),下次你调用它时它会从它停止的地方继续。所以下次它returns 1。然后2.
终于到达循环的结尾,掉出函数的结尾;协程完成了。 (这里发生的事情因我们所谈论的语言而异;在 python 中,它会抛出异常)。
协同程序为 C++ 带来了这种能力。
协程有两种;堆叠和无堆叠。
无堆栈协程仅在其状态和执行位置中存储局部变量。
stackful 协程存储整个堆栈(如线程)。
Stackless 协程可以非常轻量级。我读到的最后一个提案涉及基本上将您的函数重写成有点像 lambda 的东西;所有局部变量都进入对象的状态,标签用于跳转 to/from 协程“产生”中间结果的位置。
产生一个值的过程称为“yield”,因为协程有点像协作式多线程;您正在将执行点返回给调用者。
Boost 有一个堆栈协程的实现;它可以让你调用一个函数来为你屈服。 Stackful 协程更强大,但也更昂贵。
协程不仅仅是一个简单的生成器。您可以在协程中等待协程,这让您可以以有用的方式组合协程。
协同程序,如 if、循环和函数调用,是另一种“结构化 goto”,可让您以更自然的方式表达某些有用的模式(如状态机)。
Coroutines在C++中的具体实现有点意思
在最基本的层面上,它向 C++ 添加了一些关键字:co_return
co_await
co_yield
,以及一些与它们一起工作的库类型。
一个函数通过在其主体中包含一个协程而成为一个协程。所以从他们的声明来看,他们与函数没有区别。
当在函数体中使用这三个关键字之一时,会发生一些标准强制检查 return 类型和参数,并且函数会转换为协程。此检查告诉编译器在函数挂起时将函数状态存储在何处。
最简单的协程是生成器:
generator<int> get_integers( int start=0, int step=1 ) {
for (int current=start; true; current+= step)
co_yield current;
}
co_yield
暂停函数执行,将该状态存储在 generator<int>
中,然后 return 通过 generator<int>
存储 current
的值。
你可以遍历整数 returned.
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_resource
和 read_line
s 可能是打开文件并从中读取行的异步协程。 co_await
将 load_data
的挂起和就绪状态与其进度联系起来。
C++ 协程比这灵活得多,因为它们是在 user-space 类型之上实现的最小语言功能集。 user-space 类型有效地定义了 co_return
co_await
和 co_yield
mean ——我见过人们用它来实现monadic 可选表达式使得空可选上的 co_await
自动将空状态传播到外部可选:
modified_optional<int> add( modified_optional<int> a, modified_optional<int> b ) {
co_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;
}
协程就像一个 C 函数,它有多个 return 语句,当第二次调用时,它不会在函数的开头开始执行,而是在前一个执行后的第一条指令处开始执行 return.此执行位置与非协程函数中将存在于堆栈中的所有自动变量一起保存。
Microsoft 之前的实验性协程实现确实使用了复制堆栈,因此您甚至可以 return 来自深层嵌套函数。但是这个版本被 C++ 委员会否决了。例如,您可以使用 Boosts 纤维库获得此实现。