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

Pavan Chandaka:

什么是中的协程

它与“ Parallelism2”或/和“ Concurrency2”有何不同(请看下图)?

下图来自ISOCPP。

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

在此处输入图片说明

Yakk-Adam Nevraumont:

在抽象层次上,协程将具有执行状态的想法与具有执行线程的想法分开。

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

Threading has multiple "threads of execution" and multiple execution states. You have more than one program, and more than one thread of execution.

Coroutines has multiple execution states, but does not own a thread of execution. You have a program, and the program has state, but it has no thread of execution.


The easiest example of coroutines are generators or enumerables from other languages.

In pseudo code:

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

The Generator is called, and the first time it is called it returns 0. Its state is remembered (how much state varies with implementation of coroutines), and the next time you call it it continues where it left off. So it returns 1 the next time. Then 2.

Finally it reaches the end of the loop and falls off the end of the function; the coroutine is finished. (What happens here varies based on language we are talking about; in python, it throws an exception).

Coroutines bring this capability to C++.

There are two kinds of coroutines; stackful and stackless.

A stackless coroutine only stores local variables in its state and its location of execution.

A stackful coroutine stores an entire stack (like a thread).

Stackless coroutines can be extremely light weight. The last proposal I read involved basically rewriting your function into something a bit like a lambda; all local variables go into the state of an object, and labels are used to jump to/from the location where the coroutine "produces" intermediate results.

The process of producing a value is called "yield", as coroutines are bit like cooperative multithreading; you are yielding the point of execution back to the caller.

Boost has an implementation of stackful coroutines; it lets you call a function to yield for you. Stackful coroutines are more powerful, but also more expensive.


There is more to coroutines than a simple generator. You can await a coroutine in a coroutine, which lets you compose coroutines in a useful manner.

Coroutines, like if, loops and function calls, are another kind of "structured goto" that lets you express certain useful patterns (like state machines) in a more natural way.


The specific implementation of Coroutines in C++ is a bit interesting.

At its most basic level, it adds a few keywords to C++: co_return co_await co_yield, together with some library types that work with them.

A function becomes a coroutine by having one of those in its body. So from their declaration they are indistinguishable from functions.

When one of those three keywords are used in a function body, some standard mandated examining of the return type and arguments occurs and the function is transformed into a coroutine. This examining tells the compiler where to store the function state when the function is suspended.

The simplest coroutine is a generator:

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

co_yield suspends the functions execution, stores that state in the generator<int>, then returns the value of current through the generator<int>.

You can loop over the integers returned.

co_await meanwhile lets you splice one coroutine onto another. If you are in one coroutine and you need the results of an awaitable thing (often a coroutine) before progressing, you co_await on it. If they are ready, you proceed immediately; if not, you suspend until the awaitable you are waiting on is ready.

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 is a coroutine that generates a std::future when the named resource is opened and we manage to parse to the point where we found the data requested.

open_resource and read_lines are probably async coroutines that open a file and read lines from it. The co_await connects the suspending and ready state of load_data to their progress.

C ++协程比这更加灵活,因为它们是作为用户空间类型之上的最少语言功能集实现的。用户空间类型有效地定义了含义co_return co_awaitco_yield 含义 -我已经看到人们使用它来实现单子可选表达式,以便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;
}

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章