Coroutinen in C ++ 20. Teil 1

Einführung



Dieser Artikel ist eine Übersetzung eines Kapitels aus Rainer Grimms Buch Concurrency with Modern C ++ , das eine verfeinerte und umfangreichere Version des Artikels auf seiner Website darstellt . Da die gesamte Übersetzung je nach Reaktion auf die Veröffentlichung nicht in den Rahmen dieses Artikels passt, werde ich den Rest veröffentlichen.



Coroutinen



Coroutinen sind Funktionen, die ihre Ausführung unter Beibehaltung ihres Status anhalten oder fortsetzen können. Die Entwicklung der Funktionen in C ++ hat einen Schritt nach vorne gemacht. Coroutinenam wahrscheinlichsten enthalten C ++ 20 eingegeben.



Die in C ++ 20 neu eingeführte Idee von Coroutinen ist ziemlich alt. Das Konzept der Coroutine wurde von Melvin Conway vorgeschlagen . Dieses Konzept verwendete er 1963 in seiner Compiler-Design- Publikation. Donald Knuth nannte Prozeduren einen Sonderfall von Coroutinen. Manchmal braucht es Zeit, bis diese oder jene Idee akzeptiert wird.



Mit neuen Schlüsselwörtern co_awaitund co_yieldC ++ 20 wird das Konzept der Funktionsausführung in C ++ um zwei neue Konzepte erweitert.



Dank dessen co_await expressionwird es möglich, die Ausführung anzuhalten und fortzusetzen expression. Bei Verwendung co_await expressionin einer Funktion wird der funcAufruf auto getResult = func()nicht blockiert, wenn das Ergebnis dieser Funktion nicht verfügbar ist. Anstelle einer ressourcenintensiven Blockierung wird ein ressourcenfreundliches Warten durchgeführt.



co_yield expressionermöglicht die Implementierung von Generatorfunktionen. Generatoren sind Funktionen, die bei jedem nachfolgenden Aufruf einen neuen Wert zurückgeben. Die Generatorfunktion ähnelt Datenströmen, aus denen Werte abgerufen werden können. Datenströme können endlos sein. Daher sind diese Konzepte für eine verzögerte Auswertung in C ++ von grundlegender Bedeutung.



Generatorfunktionen



. 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
    • genannt promise.return_value(expression)für co_return expression, wo expressionder Typ anders alsvoid
    • löscht den gesamten Stapel der erstellten Variablen
    • aufgerufene promise.final_suspend()und erwartete co_awaitErgebnisse
  • Die Coroutine wird zerstört (durch Abschluss über co_return, eine nicht behandelte Ausnahme oder über den Coroutine-Handle).

    • Der Destruktor des Versprechensobjekts wird aufgerufen
    • Der Destruktor der Funktionsparameter wird aufgerufen
    • Gibt den vom Coroutine-Frame verwendeten Speicher frei
    • Übergabe der Ausführung an den Anrufer


Wenn die Coroutine mit einer nicht behandelten Ausnahme endet, geschieht Folgendes:



  • Die Ausnahme wird promise.unhandled_exception()vom catch-Block abgefangen und aufgerufen
  • aufgerufenes promise.final_suspend()und erwartetes co_awaitErgebnis


Teil 2




All Articles