Corutinas en C ++ 20. Parte 1

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_awaity co_yieldC ++ 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 expressionen una función, la funcllamada 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 expressionle 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), , , .

:



  1. .
  2. 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 . promise suspend_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)para co_return expression, donde expressiones 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()y co_awaitresultado esperado


Parte 2




All Articles