विषय पर बढ़ें

अंतिम अद्यतन:2026-10-12

C++20 coroutines परिचय

1. Coroutines क्या हैं

C++20 के बाद, C++ में आख़िरकार coroutines का समर्थन आ गया, C++23 को अतिरिक्त समर्थन लाना चाहिए, कम से कम सबसे आम use cases को कवर करना चाहिए।

Coroutine (coroutine) कंप्यूटर प्रोग्राम के एक वर्ग के components हैं, जो cooperative multitasking के subroutines का सामान्यीकरण करते हैं, और निष्पादन के निलंबित (suspend) और पुनः आरंभ (resume) होने की अनुमति देते हैं। Subroutine की तुलना में, coroutine अधिक सामान्य और लचीला है, लेकिन व्यवहार में subroutine जितना व्यापक उपयोग नहीं होता। Coroutines एक-दूसरे से परिचित प्रोग्राम components क्रियान्वयन करने के लिए अधिक उपयुक्त हैं, जैसे cooperative multitasking, exception handling, event loop, iterators, infinite lists और pipes।

विकिपीडिया के एक अंश ने coroutine के मूल विचार का वर्णन किया है।

C++ के नज़रिए से, coroutine को सरलता से समझा जा सकता है: कोई भी फ़ंक्शन जिसमें co_return, co_yield या co_await हो।

फिलहाल C++20 coroutines फ़ंक्शन ऑब्जेक्ट्स पर syntactic sugar है, कंपाइलर coroutine के चारों ओर एक कोड framework उत्पन्न करता है। यह कोड उपयोगकर्ता-परिभाषित promise types पर निर्भर करता है। C++23 के बाद इसमें बदलाव हो सकता है, लेकिन तब तक हम C++20 के नियमों पर चलते हैं, और इन प्रकारों को खुद लिखना होगा।

पहले उल्लेख किए co_return, co_yield, co_await की चर्चा आगे के सेक्शनों में होगी।

2.Step By Step Coroutine

आगे, हम एक सरल coroutine उदाहरण से शुरू करते हैं, नीचे वाला coroutine कुछ नहीं करता, हम इसे HelloTask coroutine कहते हैं।

#include <coroutine>
struct HelloTask {
    struct promise_type {
        HelloTask get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
    };
};
HelloTask myCoroutine() {
    co_return; // make it a coroutine
}
int main() {
    HelloTask x = myCoroutine();
}

इस कोड को compile कर चलाएँ:

g++ hello.cpp -fcoroutines -std=c++20

आपके कंपाइलर का C++20 विशेषताओं का समर्थन चाहिए।

ऊपर से नीचे तक इस HelloWorld प्रोग्राम को देखते हैं, रोज़मर्रा के development से क्या भिन्न है।

2.1 Header files

सबसे पहले header file में coroutine शामिल किया गया है, यहाँ मैं सीधे gcc-10 का सोर्स कोड लेता हूँ, देखते हैं आख़िर क्या काम होता है।

https://github.com/gcc-mirror/gcc/blob/releases/gcc-10/libstdc++-v3/include/std/coroutine

इसमें ये सामग्री शामिल है:

// 用于发现协程promise_type的特征类型
// 核心代码:using promise_type = typename _Result::promise_type;
1.coroutine_traits

// 协程句柄,用于管理暂停或执行的协程以及Promise
2.coroutine_handle
  // coroutine_handle全特化版本  是其他特化的公开基类。它保有handle的底层     地址。
  2.1 coroutine_handle<void>

  // coroutine_handle泛化版本
  2.2 template <typename _Promise>
      struct coroutine_handle : coroutine_handle<>


  // coroutine_handle全特化版本 处理无操作协程
  2.3 template <>
      struct coroutine_handle<noop_coroutine_promise> : public          coroutine_handle<>
3.平凡多可等待对象 Trivial awaitables
  // await表达式应该暂停
  3.1 suspend_always
  // await表达式不应该暂停
  3.2 suspend_never

देखा जा सकता है कि इसमें कई विशिष्ट terms शामिल हैं:

  • coroutine_handle
  • Promise
  • awaitable

आदि।

आगे की coroutine सामग्री इन्हीं के इर्द-गिर्द घूमेगी।

2.2 Promise

हम ऊपर वाले HelloTask पर चलते रहेंगे, तो पाएँगे कि इसमें ऐसा एक हिस्सा है:

struct HelloTask {
    struct promise_type {
        HelloTask get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
    };
};

तो सवाल यह है, यहाँ promise_type क्यों लिखना पड़ता है?

promise_type इसलिए लिखा जाता है क्योंकि हमने ऊपर 2.1 में handle का उल्लेख किया, जब कंपाइलर coroutine का handle बनाता है, तो उस coroutine ऑब्जेक्ट का promise_type निर्दिष्ट करना पड़ता है, इसलिए सभी coroutine ऑब्जेक्ट्स में मूल रूप से एक HelloTask::promise_type संरचना होती है। ध्यान देने वाले मित्र तुरंत समझ गए होंगे, यहाँ promise_type पास किया जाता है, Handle में उसे parse किया जाता है, और इससे वर्तमान उपयोगकर्ता-परिभाषित coroutine ऑब्जेक्ट के व्यवहार का निर्णय होता है।

आगे देखते हैं promise_type में क्या है, इसकी सारी सामग्री निश्चित है, आगे की सामग्री जैसे-जैसे बढ़ेगी, involved फ़ंक्शन भी बढ़ते जाएँगे, उदाहरण के लिए यहाँ return_void उपयोग हुआ है, तो जब आगे co_return से ऑब्जेक्ट लौटाया जाएगा, तो दूसरा फ़ंक्शन उपयोग होगा।

get_return_object वर्तमान उपयोगकर्ता-परिभाषित ऑब्जेक्ट लौटा सकता है, यहाँ कुछ नहीं किया गया, ऊपर 2.1 को जारी रखते हुए, हम coroutine_handle का उपयोग कर सकते हैं।

struct promise_type {
    using Handle = std::coroutine_handle<promise_type>;
    HelloTask get_return_object() {
        return HelloTask{Handle::from_promise(*this)};
    }
  // other member function
}

देखा जा सकता है कि यहाँ coroutine_handle के from_promise मेंबर से एक ऑब्जेक्ट construct किया गया है।

initial_suspend: कंपाइलर को बताता है कि coroutine पहली बार निष्पादित होने पर suspend रहे या नहीं, या यह दर्शाता है कि coroutine instantiate होते समय suspend रहे या नहीं।

final_suspend: initial_suspend के विपरीत, coroutine के फ़ंक्शन body पूरा होने के बाद, coroutine के अंत में एक final_suspend होता है। इससे आपको अतिरिक्त cleanup का मौका मिलता है।

ये दोनों awaitable लौटाते हैं, यहाँ header file का suspend_always अपनाया गया है, बेशक उपयोगकर्ता awaitable भी custom परिभाषित कर सकता है, इसकी चर्चा आगे के लेखों में होगी।

suspend_always और suspend_never का सोर्स इस प्रकार है:

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 {}
};

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 {}
};

इसमें कुछ मेंबर शामिल हैं,

  • await_ready
  • यदि पता हो कि परिणाम पहले से तैयार है तो synchronously पूरा हो जाएँ?
  • await_suspend
  • रिटर्न वैल्यू bool, void या coroutine handle हो सकती है।
  • यहाँ void लौटाया जाता है, तो तुरंत नियंत्रण वर्तमान Coroutine के caller/resumer को लौटा दिया जाता है (यह Coroutine अब भी suspend रहता है), अन्यथा यदि await_suspend से bool लौटे, तो true का अर्थ है नियंत्रण caller/resumer को सौंपना, false का अर्थ है वर्तमान coroutine resume करना। Coroutine handle लौटाने की चर्चा आगे के लेखों में होगी।
  • await_resume
  • इसका परिणाम पूरे co_await एक्सप्रेशन का परिणाम है। इसलिए रिटर्न वैल्यू void ही नहीं, custom ऑब्जेक्ट जैसी भी हो सकती है।

return_void: जब co_return; उपयोग होता है, तो return_void कॉल होता है।

unhandled_exception: exceptions पकड़ने के लिए।

अंत में, coroutine बनाने का एक क़दम बाक़ी है, शुरुआती परिभाषा के अनुसार coroutine कोई भी फ़ंक्शन है जिसमें co_return, co_yield या co_await हो। इसलिए यह बना:

HelloTask myCoroutine() {
    co_return; // make it a coroutine
}

2.3 पूरा flow

पूरा call flow क्या है? इस सेक्शन में ऊपर वाले कोड के निष्पादन logic का विस्तार से वर्णन करते हैं, शुरू करने से पहले हम कोड बदलकर कुछ print ऑपरेशन जोड़ते हैं:

#include <iostream>
#include <coroutine>
struct HelloTask {
    struct promise_type {
        HelloTask get_return_object() { std::cout << "get_return_object" << std::endl; return { }; }
        std::suspend_never initial_suspend() { std::cout << "initial_suspend" << std::endl; return {}; }
        std::suspend_never final_suspend() noexcept { std::cout << "final_suspend" << std::endl;return {}; }
        void return_void() { std::cout << "return_void" << std::endl; }
        void unhandled_exception() { std::cout << "unhandled_exception" << std::endl; }
    };
};
HelloTask myCoroutine() {
    std::cout << "create coroutine" << std::endl;
    co_return; // make it a coroutine
}
int main() {
    std::cout << "start main" << std::endl;
    HelloTask x = myCoroutine();
    std::cout << "end main" << std::endl;
}

तब आउटपुट:

start main
get_return_object
initial_suspend
create coroutine
return_void
final_suspend
end main

flow इस प्रकार है:

  • start main
  • heap पर coutine ऑब्जेक्ट allocate होता है, promise construct होता है, promise.get_return_object कॉल होकर वर्तमान ऑब्जेक्ट लौटाता है (तब get_return_object आउटपुट हुआ), यह इसलिए किया गया ताकि परिणाम local वेरिएबल में save हो।
  • promise.initial_suspend, तब initial_suspend आउटपुट हुआ, यहाँ co_await trigger होता है, create coroutine आउटपुट होता है, coroutine body निष्पादित होने के बाद, co_return कॉल होता है।
  • co_return, promise.return_void कॉल करता है, return_void आउटपुट होता है।
  • अंत में, promise.final_suspend, final_suspend आउटपुट करता है।
  • end main

coroutines से जुड़ी सामग्री में और भी बहुत कुछ है, यह सेक्शन शुरुआत है, आगे लगातार अपडेट होता रहेगा~

संबंधित विषय

टिप्पणियाँ