Introduction
This article is a translation of a chapter from Rainer Grimm's book Concurrency with Modern C ++ , which is a more refined and extensive version of the article on his website . Since the entire translation does not fit within the framework of this article, depending on the reaction to the publication, I will post the rest.
Coroutines
Coroutines are functions that can pause or resume their execution while maintaining their state. The evolution of functions in C ++ has taken a step forward. Coroutinesmost likely to include entered C ++ 20.
The idea of coroutines, introduced as new in C ++ 20, is pretty old. The concept of coroutine was proposed by Melvin Conway . He used this concept in his 1963 compiler design publication. Donald Knuth called procedures a special case of coroutines. Sometimes it takes time for this or that idea to be accepted.
With new keywords co_await
and co_yield
C ++ 20, it expands the concept of function execution in C ++ with two new concepts.
Thanks to it co_await expression
, it becomes possible to pause and resume execution expression
. When used co_await expression
in a function, the func
call is auto getResult = func()
not blocking if the result of the given function is not available. Instead of a resource-consuming blocking, a resource-friendly waiting is performed.
co_yield expression
allows you to implement generator functions. Generators are functions that return a new value with each subsequent call. The generator function is similar to data streams from which values can be retrieved. Data streams can be endless. Thus, these concepts are fundamental to lazy evaluation in C ++.
Generator functions
. getNumbers
begin
end
inc
. begin
end
, inc
.
// greedyGenerator.cpp
#include <iostream>
#include <vector>
std::vector<int> getNumbers(int begin, int end, int inc = 1) {
std::vector<int> numbers; // (1)
for (int i = begin; i < end; i += inc) {
numbers.push_back(i);
}
return numbers;
}
int main() {
const auto numbers = getNumbers(-10, 11);
for (auto n : numbers) {
std::cout << n << " ";
}
std::cout << "\n";
for (auto n : getNumbers(0, 101, 5)) {
std::cout << n << " ";
}
std::cout << "\n";
}
, getNumbers
, std::iota C++11.
, :
$ ./greedyGenerator
-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10
0 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 100
. -, numbers
(. (1)
) . 5 1000 . -, getNumbers
.
// lazyGenerator.cpp
#include <iostream>
#include <vector>
generator<int> generatorForNumbers(int begin, int inc = 1) {
for (int i = begin; ; i += inc) { // (4)
co_yield i; // (3)
}
}
int main() {
const auto numbers = generatorForNumbers(-10); // (1)
for (int i = 1; i <= 20; ++i) { // (5)
std::cout << numbers << " ";
}
std::cout << "\n";
for (auto n : generatorForNumbers(0, 5)) { // (2)
std::cout << n << " ";
}
std::cout << "\n";
}
: , .. . .
, getNumbers
greedyGenerator.cpp
std::vector<int>
, generatorForNumbers
lazyGenerator.cpp
generator
. numbers
(1)
generatorForNumbers(0, 5)
(2)
. Range-based for . , i
co_yield i
(. (3)
) . , .
generatorForNumbers(0, 5)
(. (2)
) (just-in-place usage).
. generatorForNumbers
, for (4)
. , .., , (5)
. , , (2)
.
- . - , , , . , , . , .
C++20 , (first-class) (stackless).
. , .
. , .
. . (resumable functions).
.
:
- ( ).
- , .
- .
- c , , , , .
- .
, . , - 1MB Windows 2MB Linux.
- co_return
- co_await
- co_yield
- co_await expression range-based for
return . (auto
), ().
, constexpr
, , main
.
proposal N4628.
co_return
, co_yield
co_await
co_return
.
co_yield
. generator<int> generatorForNumbers(int begin, int inc = 1)
generator<int>
promise p
, co_yield i
co_await p.yield_value(i).co_yield i
. .
co_await
, . exp
co_await exp
, , ( awaitables). exp
, : await_ready
, await_suspend
await_resume
.
C++20 2 awaitables: std::suspend_always
std::suspend_never
.
std::suspend_always
struct suspend_always {
constexpr bool await_ready() const noexcept { return false; }
constexpr void await_suspend(coroutine_handle<>) const noexcept {}
constexpr void await_resume() const noexcept {}
};
, awaitable std::suspend_always
, await_ready
false
. std::suspend_never
.
std::suspend_never
struct suspend_never {
constexpr bool await_ready() const noexcept { return true; }
constexpr void await_suspend(coroutine_handle<>) const noexcept {}
constexpr void await_resume() const noexcept {}
};
co_await
.
Acceptor acceptor{443};
while (true) {
Socket socket = acceptor.accept(); // blocking
auto request = socket.read(); // blocking
auto response = handleRequest(request);
socket.write(response); // blocking
}
. 443 , , . .
co_await
.
Acceptor acceptor{443};
while (true) {
Socket socket = co_await acceptor.accept();
auto request = co_await socket.read();
auto response = handleRequest(request);
co_await socket.write(response);
}
20 , . .
: promise , handle frame .
Promise .
Handle handle frame .
Frame , . promise , , (suspention point), , , .
:
- .
- frame .
workflow
co_return
co_yield
co_await
.
{
Promise promise;
co_await promise.initial_suspend();
try {
< >
} catch (...) {
promise.unhandled_exception();
}
FinalSuspend:
co_await promise.final_suspend();
}
Workflow :
-
- frame .
- frame .
- promise
promise
. -
promise.get_return_object()
handle . . -
promise.initial_suspend()
co_await
. promisesuspend_never
suspend_always
. -
co_await promise.initial_suspend()
-
-
promise.get_return_object()
-
-
co_return
-
promise.return_void()
co_return
co_return expression
,expression
void
- called
promise.return_value(expression)
forco_return expression
, whereexpression
is of type other thanvoid
- deletes the entire stack of created variables
- called
promise.final_suspend()
and expectedco_await
results
-
- The coroutine is destroyed (via completion via
co_return
, an unhandled exception, or via the coroutine handle)
- the destructor of the promise object is called
- the destructor of the function parameters is called
- frees memory used by coroutine frame
- passing execution to the caller
When the coroutine ends with an unhandled exception, the following happens:
- the exception is caught and called
promise.unhandled_exception()
from the catch block - called
promise.final_suspend()
and expectedco_await
result