Introducción
Este artículo es una traducción de un capítulo del libro Concurrency with Modern C ++ de Rainer Grimm , que es una versión más refinada y extensa del artículo de su sitio web . Dado que la traducción completa no encaja en el marco de este artículo, según la reacción a la publicación, publicaré el resto.
Corutinas
Las corrutinas son funciones que pueden pausar o reanudar su ejecución mientras mantienen su estado. La evolución de funciones en C ++ ha dado un paso adelante. Corutinasmás probable que incluya ingresó C ++ 20.
La idea de las corrutinas, introducida como nueva en C ++ 20, es bastante antigua. El concepto de corrutina fue propuesto por Melvin Conway . Usó este concepto en su publicación de desarrollo del compilador de 1963. Donald Knuth se refirió a los procedimientos como un caso especial de corrutinas. A veces se necesita tiempo para que se acepte tal o cual idea.
Con nuevas palabras clave co_await
y co_yield
C ++ 20, amplía el concepto de ejecución de funciones en C ++ con dos nuevos conceptos.
Gracias a él co_await expression
, es posible pausar y reanudar la ejecución expression
. Cuando se usa co_await expression
en una función, la func
llamada auto getResult = func()
no se bloquea si el resultado de la función dada no está disponible. En lugar de un bloqueo que consume muchos recursos, se lleva a cabo una espera de fácil uso.
co_yield expression
le permite implementar funciones de generador. Los generadores son funciones que devuelven un nuevo valor con cada llamada posterior. La función del generador es similar a los flujos de datos de los que se pueden recuperar valores. Los flujos de datos pueden ser infinitos. Por tanto, estos conceptos son fundamentales para la evaluación perezosa en C ++.
Funciones del generador
. 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
- llamada
promise.return_value(expression)
paraco_return expression
, dondeexpression
es del tipo que no seavoid
- elimina toda la pila de variables creadas
- resultados llamados
promise.final_suspend()
y esperadosco_await
-
- La corrutina se destruye (mediante la finalización mediante
co_return
, una excepción no controlada o mediante el identificador de corrutina)
- el destructor del objeto de promesa se llama
- el destructor de los parámetros de la función se llama
- libera la memoria utilizada por el marco de rutina
- pasar la ejecución a la persona que llama
Cuando la corrutina termina con una excepción no controlada, ocurre lo siguiente:
- la excepción se captura y se llama
promise.unhandled_exception()
desde el bloque de captura - llamado
promise.final_suspend()
yco_await
resultado esperado