Coroutines في C ++ 20. الجزء 1

المقدمة



هذه المقالة هي ترجمة لفصل من كتاب Rainer Grimm Concurrency with Modern C ++ ، وهو نسخة أكثر دقة وشمولية من المقال على موقعه على الإنترنت . نظرًا لأن الترجمة بأكملها لا تتناسب مع إطار هذه المقالة ، اعتمادًا على رد الفعل على المنشور ، سأقوم بنشر الباقي.



كوروتين



Coroutines هي وظائف يمكن أن توقف تنفيذها مؤقتًا أو تستأنف تنفيذها مع الحفاظ على حالتها. أخذ تطور الوظائف في C ++ خطوة إلى الأمام. كوروتينعلى الأرجح أن تشمل دخلت C ++ 20.



إن فكرة الكوروتين ، التي تم تقديمها على أنها جديدة في C ++ 20 ، قديمة جدًا. تم اقتراح مفهوم الكوروتين من قبل ملفين كونواي . استخدم هذا المفهوم في منشور تصميم المترجم لعام 1963. ووصف دونالد كنوث الإجراءات بحالة خاصة من coroutines. أحيانًا يستغرق قبول هذه الفكرة أو تلك وقتًا.



باستخدام الكلمات الرئيسية الجديدة co_awaitو co_yieldC ++ 20 ، يوسع مفهوم تنفيذ الوظائف في C ++ بمفهومين جديدين.



بفضل ذلك co_await expression، يصبح من الممكن إيقاف التنفيذ مؤقتًا واستئنافه expression. عند استخدامها co_await expressionفي إحدى الوظائف ، لا funcيتم auto getResult = func()حظر المكالمة إذا كانت نتيجة الوظيفة المحددة غير متوفرة. بدلاً من الحظر الذي يستهلك الموارد ، يتم إجراء انتظار ملائم للموارد.



co_yield expressionيسمح لك بتنفيذ وظائف المولد. المولدات هي وظائف تقوم بإرجاع قيمة جديدة مع كل استدعاء لاحق. تشبه وظيفة المولد تدفقات البيانات التي يمكن استرداد القيم منها. يمكن أن تكون تدفقات البيانات لا حصر لها. وبالتالي ، فإن هذه المفاهيم أساسية للتقييم الكسول في C ++.



وظائف المولد



. 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
    • ودعا promise.return_value(expression)ل co_return expression، حيث expressionهو من نوع آخر منvoid
    • يحذف مجموعة المتغيرات التي تم إنشاؤها بالكامل
    • ودعا النتائج promise.final_suspend()المتوقعةco_await
  • تم إتلاف coroutine (عن طريق الإكمال عبر co_returnاستثناء غير معالج أو عبر مقبض coroutine)

    • يسمى مدمر كائن الوعد
    • يسمى مدمر معلمات الوظيفة
    • يحرر الذاكرة المستخدمة بواسطة إطار coroutine
    • تمرير الإعدام إلى المتصل


عندما ينتهي coroutine مع استثناء غير معالج ، يحدث ما يلي:



  • تم اكتشاف الاستثناء واستدعائه promise.unhandled_exception()من كتلة catch
  • يسمى والنتيجة promise.final_suspend()المتوقعةco_await


الجزء 2




All Articles