云谷 網(wǎng)站建設(shè)網(wǎng)絡(luò)推廣的方法有
一、c++20的協(xié)程概念
??????? 在c++20標(biāo)準(zhǔn)后,在一些函數(shù)中看到co_await、co_yield、co_return這些關(guān)鍵詞,這是c++20為協(xié)程實(shí)現(xiàn)設(shè)計(jì)的運(yùn)算符。
????????協(xié)程是能暫停執(zhí)行以在之后恢復(fù)的函數(shù)。原來(lái)我們調(diào)用一個(gè)功能函數(shù)時(shí),只要調(diào)用了以后,就要完整執(zhí)行完該功能函數(shù)所有步驟(語(yǔ)句)才能回來(lái)執(zhí)行自身的步驟,對(duì)于一些功能函數(shù)其由很長(zhǎng)的執(zhí)行周期,該執(zhí)行周期中調(diào)用者就不能處理自身一些事務(wù)。在協(xié)程出現(xiàn)以前我們就需要回調(diào)、阻塞等手段綜合設(shè)計(jì)實(shí)現(xiàn)。
????????協(xié)程就是為了解決類(lèi)似這種問(wèn)題的。調(diào)用者調(diào)用協(xié)程函數(shù)后,執(zhí)行到中途可以通過(guò)co_yield暫停掛起,返回自身執(zhí)行事務(wù),然后在通過(guò)resume喚醒恢復(fù)協(xié)程,協(xié)程函數(shù)會(huì)從掛起標(biāo)識(shí)處繼續(xù)往下執(zhí)行。整個(gè)協(xié)程函數(shù)執(zhí)行周期內(nèi),可以多次返回調(diào)用自身。在協(xié)程函數(shù)結(jié)束后,通過(guò)co_return還可以返回協(xié)程結(jié)果或協(xié)程內(nèi)部對(duì)象。
????????協(xié)程是無(wú)棧的:它們通過(guò)返回到調(diào)用方暫停執(zhí)行,并且從棧分離存儲(chǔ)恢復(fù)執(zhí)行需要的數(shù)據(jù)。這樣就可以編寫(xiě)異步執(zhí)行的順序代碼(例如,不使用顯式的回調(diào)來(lái)處理非阻塞 I/O),還支持對(duì)惰性計(jì)算的無(wú)限序列上的算法及其他用途。?
??????? 例如用關(guān)鍵詞 co_yield
暫停執(zhí)行并返回一個(gè)值:
task coroutine_func(int n = 1)
{int i = 0;while(i<n){co_yield i++;}
}
??????? 或者用關(guān)鍵詞 co_return 完成執(zhí)行并返回一個(gè)值或void:
task coroutine_func(int n = 1)
{co_return;
}
二、c++20協(xié)程庫(kù)
????????這些關(guān)鍵詞都做了啥事情呢。在c++20協(xié)程庫(kù)中,提供了以下支持庫(kù)來(lái)實(shí)現(xiàn)協(xié)程:
協(xié)程特征,定義于頭文件 <coroutine>
coroutine_traits (C++20)用于發(fā)現(xiàn)協(xié)程承諾類(lèi)型的特征類(lèi)型(類(lèi)模板) 協(xié)程柄,定義于頭文件 <coroutine>
coroutine_handle (C++20)用于指代暫?;驁?zhí)行的協(xié)程(類(lèi)模板) 無(wú)操作協(xié)程,定義于頭文件 <coroutine>
noop_coroutine (C++20)創(chuàng)建在等待或銷(xiāo)毀時(shí)無(wú)可觀察作用的協(xié)程柄(函數(shù))
noop_coroutine_promise (C++20)用于無(wú)可觀察作用的協(xié)程(類(lèi))
noop_coroutine_handle (C++20)std::coroutine_handle<std::noop_coroutine_promise> ,有意用于指代無(wú)操作協(xié)程
(typedef) 平凡可等待體,定義于頭文件 <coroutine>
suspend_never (C++20)指示 await 表達(dá)式應(yīng)該決不暫停(類(lèi))
suspend_always (C++20)指示 await 表達(dá)式應(yīng)該始終暫停(類(lèi))
?????????std::coroutine_traits從協(xié)程的返回類(lèi)型與形參類(lèi)型確定承諾類(lèi)型。
//定義于頭文件 <coroutine> ,(C++20 起)
template< class R, class... Args > struct coroutine_traits;/*模板形參
*R - 協(xié)程的返回類(lèi)型
*Args - 協(xié)程的形參類(lèi)型,若協(xié)程為非靜態(tài)成員函數(shù)則包括隱式對(duì)象形參
*/
??????? 標(biāo)準(zhǔn)庫(kù)實(shí)現(xiàn)提供與 R::promise_type 相同的公開(kāi)可訪(fǎng)問(wèn)成員類(lèi)型 promise_type ,若該有限定標(biāo)識(shí)合法并代表類(lèi)型。否則它無(wú)成員。coroutine_traits 的程序定義特化應(yīng)當(dāng)定義公開(kāi)可訪(fǎng)問(wèn)的成員類(lèi)型 promise_type ,否則行為未定義。
//成員類(lèi)型
//類(lèi)型 定義
promise_type R::promise_type //若它合法,或由程序定義特化提供
???????? 2.1 coroutine_handle句柄
????????其中最主要的就是coroutine_handle句柄,類(lèi)模板 coroutine_handle 能用于指代暫?;驁?zhí)行的協(xié)程,定義于頭文件 <coroutine>:
//(C++20 起)
/*結(jié)構(gòu)體主模板,可從 Promise 類(lèi)型的承諾對(duì)象創(chuàng)建。*/
template< class Promise = void > struct coroutine_handle; /*特化std::coroutine_handle<void>擦除承諾類(lèi)型。它可從其他特化轉(zhuǎn)換*
template<> struct coroutine_handle<void>; /*特化std::coroutine_handle<std::noop_coroutine_promise>指代無(wú)操作協(xié)程。不能從承諾對(duì)象創(chuàng)建它*/
template<> struct coroutine_handle<std::noop_coroutine_promise>;
using noop_coroutine_handle =std::coroutine_handle<std::noop_coroutine_promise>;
????????std::coroutine_handle 的每個(gè)特化均為可平凡復(fù)制 (TriviallyCopyable) ,并保有一個(gè)指向協(xié)程狀態(tài)的指針作為其僅有的非靜態(tài)成員。添加 coroutine_handle 的特化的程序行為未定義。std::coroutine_handle 功能如下:
//(C++20)
成員函數(shù)
(構(gòu)造函數(shù)) 構(gòu)造 coroutine_handle 對(duì)象(公開(kāi)成員函數(shù))
operator= 賦值 coroutine_handle 對(duì)象(公開(kāi)成員函數(shù))
from_promise [靜態(tài)]從協(xié)程的承諾對(duì)象創(chuàng)建 coroutine_handle(公開(kāi)靜態(tài)成員函數(shù)) 轉(zhuǎn)換
operator coroutine_handle<> 獲得擦除類(lèi)型的 coroutine_handle(公開(kāi)成員函數(shù)) 觀察器
done 檢查協(xié)程是否已完成(公開(kāi)成員函數(shù))
operator bool 檢查柄是否表示協(xié)程(公開(kāi)成員函數(shù)) 控制
operator()
resume 恢復(fù)協(xié)程執(zhí)行(公開(kāi)成員函數(shù))
destroy 銷(xiāo)毀協(xié)程(公開(kāi)成員函數(shù)) 承諾訪(fǎng)問(wèn)
promise 訪(fǎng)問(wèn)協(xié)程的承諾對(duì)象(公開(kāi)成員函數(shù)) 導(dǎo)出/導(dǎo)入
address 導(dǎo)出底層地址,即支撐協(xié)程的指針(公開(kāi)成員函數(shù))
from_address [靜態(tài)]從指針導(dǎo)入?yún)f(xié)程(公開(kāi)靜態(tài)成員函數(shù)) 非成員函數(shù)
operator== 比較二個(gè) coroutine_handle 對(duì)象(函數(shù))
operator<=> 比較二個(gè) coroutine_handle 對(duì)象(函數(shù)) 輔助類(lèi)
std::hash<std::coroutine_handle> std::coroutine_handle 的散列支持(類(lèi)模板特化)
????????std::coroutine_handle,協(xié)程句柄是從協(xié)程外部操縱的,這是用于恢復(fù)協(xié)程執(zhí)行或銷(xiāo)毀協(xié)程幀的非擁有柄;承諾(promise)對(duì)象,從協(xié)程內(nèi)部操縱,協(xié)程通過(guò)此對(duì)象提交其結(jié)果或異常。
??????? 2.2 std::coroutine_handle實(shí)現(xiàn)案例
????????現(xiàn)在來(lái)看如何通過(guò)std::coroutine_handle實(shí)現(xiàn)協(xié)程函數(shù)的,下面定義一個(gè)簡(jiǎn)單的協(xié)程例子:
//test0.h
#ifndef _TEST_0_H_
#define _TEST_0_H_
void coroutine_first_test(void);
#endif //_TEST_0_H_
//test0.cpp
#include "test0.h"#include <coroutine>
#include <iostream>struct task {struct promise_type {task get_return_object() { std::cout << "task::promise_type.get_return_object \n";return task{Handle::from_promise(*this)}; }//返回std::suspend_never(這個(gè)隨后說(shuō)明) ,初始化后就繼續(xù)運(yùn)行std::suspend_never initial_suspend() { std::cout << "task::promise_type.initial_suspend \n";return {}; }std::suspend_never final_suspend() noexcept { std::cout << "task::promise_type.final_suspend \n";return {}; }std::suspend_always yield_value(const int &val) noexcept { //co_yield調(diào)用std::cout << "task::promise_type.yield_value " << val << "\n";return {};}void return_void() {} //co_return調(diào)用void unhandled_exception() {std::cout << "task::promise_type.unhandled_exception \n";}};using Handle = std::coroutine_handle<promise_type>;//協(xié)程句柄explicit task(Handle coroutine) : m_coroutine{coroutine} {} //get_return_object時(shí)調(diào)用task() = default;~task() { std::cout << "~task \n";if (m_coroutine) //自行銷(xiāo)毀{m_coroutine.destroy(); }}// task(const task&) = delete;// task& operator=(const task&) = delete;Handle m_coroutine; //協(xié)程句柄
};task coroutine_func(int n = 0)
{int i = 0;while(i<n){co_yield i++;std::cout << "coroutine dosomthing" << i << "\n";}co_return;
}void coroutine_first_test(void)
{auto c0_obj = coroutine_func(10);for (size_t i = 0; i < 5; i++){c0_obj.m_coroutine.resume();//喚醒協(xié)程std::cout << "caller dosomthing" << i << "\n";}
};
//main.cpp
#include "test0.h"int main(int argc, char* argv[])
{coroutine_first_test();return 0;
};
??????? task是個(gè)自定義的結(jié)構(gòu)體,為了能作為協(xié)程的返回值,需要定義一個(gè)內(nèi)部 promise_type結(jié)構(gòu)體。
??????? 【1】協(xié)程開(kāi)始執(zhí)行時(shí),它進(jìn)行下列操作:
- 用 operator new 分配協(xié)程狀態(tài)對(duì)象(
coroutine state
)。 - 將所有函數(shù)形參復(fù)制到協(xié)程狀態(tài)中:按值傳遞的形參被移動(dòng)或復(fù)制,按引用傳遞的參數(shù)保持為引用(因此,如果在被指代對(duì)象的生存期結(jié)束后恢復(fù)協(xié)程,它可能變成懸垂引用),上述例如傳入的是int型引用。
- 調(diào)用承諾對(duì)象的構(gòu)造函數(shù)(
promise
)。如果承諾類(lèi)型擁有接收所有協(xié)程形參的構(gòu)造函數(shù),那么以復(fù)制后的協(xié)程實(shí)參調(diào)用該構(gòu)造函數(shù)。否則調(diào)用其默認(rèn)構(gòu)造函數(shù)。這里采用默認(rèn)構(gòu)造函數(shù),沒(méi)具名給出。 - 調(diào)用 promise.get_return_object() 并將其結(jié)果在局部變量中保持。該調(diào)用的結(jié)果將在協(xié)程首次暫停時(shí)返回給調(diào)用方。至此并包含這個(gè)步驟為止,任何拋出的異常均傳播回調(diào)用方,而非置于承諾中。
- 調(diào)用 promise.initial_suspend() 。典型的承諾類(lèi)型要么(對(duì)于惰性啟動(dòng)的協(xié)程)返回 std::suspend_always,要么(對(duì)于急切啟動(dòng)的協(xié)程)返回 std::suspend_never。
- ? ? ? ? *如果返回std::suspend_never,表示await表達(dá)式應(yīng)該決不暫停,立即執(zhí)行,不掛起
- ? ? ? ? *返回std::suspend_always,表示await表達(dá)式應(yīng)該始終暫停,不立即執(zhí)行,先掛起
- 當(dāng) co_await promise.initial_suspend() 恢復(fù)時(shí),開(kāi)始協(xié)程體的執(zhí)行。
??????? 【2】當(dāng)協(xié)程抵達(dá)暫停點(diǎn)時(shí):
- 將先前獲得的返回對(duì)象返回給調(diào)用方/恢復(fù)方,這里是coroutine_first_test函數(shù),如果需要?jiǎng)t先隱式轉(zhuǎn)換到協(xié)程的返回類(lèi)型。
- 協(xié)程從暫停點(diǎn)返回通過(guò)co_yield,本質(zhì)上是調(diào)用了promise.yield_value(表達(dá)式)
- 調(diào)用方/恢復(fù)方,coroutine_first_test函數(shù)通過(guò)調(diào)用coroutine_handle的resume告知協(xié)程恢復(fù)執(zhí)行,協(xié)程函數(shù)coroutine_func重新從co_yield語(yǔ)句的下一句開(kāi)始執(zhí)行。
??????? 【3】當(dāng)協(xié)程抵達(dá) co_return 語(yǔ)句時(shí),它進(jìn)行下列操作:
- 若是co_return,調(diào)用 promise.return_void(),如果承諾類(lèi)型 Promise 沒(méi)有 Promise::return_void() 成員函數(shù)(本例所采用),那么則行為未定義。
- 若是co_return expr,調(diào)用 promise.return_value(expr),其中 expr 具有非 void 類(lèi)型或 void 類(lèi)型,如果承諾類(lèi)型 Promise 沒(méi)有 Promise::return_value() 成員函數(shù)(本例子沒(méi)定義),那么則行為未定義。
- 控制流出返回時(shí),協(xié)程結(jié)束開(kāi)始結(jié)束運(yùn)行。此時(shí)以創(chuàng)建的逆序銷(xiāo)毀所有具有自動(dòng)存儲(chǔ)期的變量。
- 調(diào)用 promise.final_suspend() 。
??????? 【4】如果協(xié)程因未捕捉的異常結(jié)束,那么它進(jìn)行下列操作:
- 捕捉異常并在 catch 塊內(nèi)調(diào)用 promise.unhandled_exception()
- 調(diào)用 promise.final_suspend() (例如,以恢復(fù)某個(gè)繼續(xù)或發(fā)布其結(jié)果)。此時(shí)開(kāi)始恢復(fù)協(xié)程是未定義行為。
??????? 【5】協(xié)程當(dāng)經(jīng)由 co_return 或未捕捉異常而正常終止,它進(jìn)行下列操作:
- 調(diào)用承諾對(duì)象的析構(gòu)函數(shù)(~promise_type,默認(rèn)析構(gòu))。
- 調(diào)用各個(gè)函數(shù)形參副本的析構(gòu)函數(shù)(本例只有int型引用)。
- 調(diào)用 operator delete 以釋放協(xié)程狀態(tài)所用的內(nèi)存(~task)。
- 轉(zhuǎn)移執(zhí)行回到調(diào)用方/恢復(fù)方(coroutine_first_test)。
???????? 這里的協(xié)程函數(shù)返回對(duì)象采用的是 std::suspend_never等待體,標(biāo)準(zhǔn)庫(kù)定義了兩個(gè)平凡的可等待體:std::suspend_always 及 std::suspend_never,先說(shuō)std::suspend_never:
//std::suspend_never,定義于頭文件 <coroutine>,
//suspend_never 是空類(lèi),能用于指示 await 表達(dá)式絕不暫停并且不產(chǎn)生值。/*成員函數(shù)*/
/*(C++20 起)
*std::suspend_never::await_ready,指示 await 表達(dá)式絕不暫停(公開(kāi)成員函數(shù))
*始終返回 true ,指示 await 表達(dá)式絕不暫停。
*/
constexpr bool await_ready() const noexcept { return true; }/*(C++20 起)
*std::suspend_never::await_suspend,無(wú)操作(公開(kāi)成員函數(shù))
*不做任何事。
*/
constexpr void await_suspend() const noexcept {}/*(C++20 起)
*std::suspend_never::await_resume,無(wú)操作(公開(kāi)成員函數(shù))
*不做任何事。若使用 suspend_never 則 await 表達(dá)式不產(chǎn)生值。
*/
constexpr void await_resume() const noexcept {}
??????? 編譯g++ main.cpp test*.cpp -o test.exe -std=c++20,運(yùn)行程序很好展示了上述邏輯過(guò)程:
???????? 2.3 承諾類(lèi)型(Promise)
????????承諾類(lèi)型(Promise),獲得到承諾對(duì)象的引用。若 *this 不指代承諾對(duì)象尚未被銷(xiāo)毀的協(xié)程,則行為未定義。此函數(shù)不對(duì)特化 std::coroutine_handle<> 提供。
//std::coroutine_handle<Promise>::promise
//主模板的成員
Promise& promise() const;
//特化 std::coroutine_handle<std::noop_coroutine_promise> 的成員
std::noop_coroutine_promise& promise() const noexcept;
????????編譯器用 std::coroutine_traits 從協(xié)程的返回類(lèi)型確定承諾類(lèi)型。
正式而言,,如果定義它為非靜態(tài)成員函數(shù),以如下方式確定它的承諾類(lèi)型 :
/*
*令 R 與 Args... 分別代表協(xié)程的返回類(lèi)型與參數(shù)類(lèi)型列表,
*ClassT 與 /*cv限定*/ (如果存在)分別代表協(xié)程所屬的類(lèi)與其 cv 限定
*/
std::coroutine_traits<R, Args...>::promise_type //如果不定義協(xié)程為非靜態(tài)成員函數(shù)。
std::coroutine_traits<R, ClassT /*cv限定*/&, Args...>::promise_type //如果定義協(xié)程為非右值引用限定的非靜態(tài)成員函數(shù)。
?std::coroutine_traits<R, ClassT /*cv限定*/&&, Args...>::promise_type //如果定義協(xié)程為右值引用限定的非靜態(tài)成員函數(shù)。
????????例如,如果上述結(jié)構(gòu)體task定義為結(jié)構(gòu)體模板,template<typename T>? struct task,其協(xié)程函數(shù)定義:
//如果定義協(xié)程為
task<float> foo(std::string x, bool flag);
//那么它的承諾類(lèi)型是
std::coroutine_traits<task<float>, std::string, bool>::promise_type。//如果定義協(xié)程為
task<void> my_class::method1(int x) const;
//那么它的承諾類(lèi)型是 std::coroutine_traits<task<void>, const my_class&, int>::promise_type。//如果定義協(xié)程為
task<void> my_class::method1(int x) &&;
//那么它的承諾類(lèi)型是
std::coroutine_traits<task<void>, my_class&&, int>::promise_type。
????????每個(gè)協(xié)程均與下列對(duì)象關(guān)聯(lián):
- 承諾(promise)對(duì)象。
- 協(xié)程句柄 (coroutine handle)。
- 協(xié)程狀態(tài) (coroutine state),它是一個(gè)包含以下各項(xiàng)的分配于堆(除非優(yōu)化掉其分配)的內(nèi)部對(duì)象:
- 承諾對(duì)象
- 各個(gè)形參(全部按值復(fù)制)
- 當(dāng)前暫停點(diǎn)的某種表示,使得恢復(fù)時(shí)程序知曉要從何處繼續(xù),銷(xiāo)毀時(shí)知曉有哪些局部變量在作用域內(nèi)
- 生存期跨過(guò)當(dāng)前暫停點(diǎn)的局部變量和臨時(shí)量
????????協(xié)程狀態(tài)由非數(shù)組 operator new 在堆上分配。如果承諾類(lèi)型定義了類(lèi)級(jí)別的替代函數(shù),那么會(huì)使用它,否則會(huì)使用全局的 operator new;如果承諾類(lèi)型定義了接收額外形參的 operator new 的布置形式,且它們所匹配的實(shí)參列表中的第一實(shí)參是要求的大小(std::size_t 類(lèi)型),而其余則是各個(gè)協(xié)程函數(shù)實(shí)參,那么將這些實(shí)參傳遞給 operator new(這使得能對(duì)協(xié)程使用前導(dǎo)分配器約定)
????????如果分配失敗,那么協(xié)程拋出 std::bad_alloc,除非承諾類(lèi)型 Promise 類(lèi)型定義了成員函數(shù) Promise::get_return_object_on_allocation_failure()。如果定義了該成員函數(shù),那么使用 operator new 的 nothrow 形式進(jìn)行分配,而在分配失敗時(shí),協(xié)程會(huì)立即將從 Promise::get_return_object_on_allocation_failure() 獲得的對(duì)象返回給調(diào)用方。
??????? 2.4 承諾類(lèi)型-協(xié)程返回類(lèi)型及協(xié)程函數(shù)的交互
??????? 下來(lái)看一下一個(gè)更復(fù)雜的例子,可以從協(xié)程傳遞( co_yield、co_return)回引用,實(shí)現(xiàn)協(xié)程與調(diào)用者的交互。
//test1.h
#ifndef _TEST_1_H_
#define _TEST_1_H_
void coroutine_model_test(void);
#endif //_TEST_1_H_
//test1.cpp
#include "test1.h"#include <coroutine>
#include <iostream>
#include <optional>
#include <ranges>template<typename T> requires std::movable<T>
class Task {
public://promise_type就是承諾對(duì)象,承諾對(duì)象用于協(xié)程內(nèi)外交流struct promise_type {//生成協(xié)程返回,會(huì)在協(xié)程正在運(yùn)行前進(jìn)行調(diào)用Task<T> get_return_object() {std::cout << "get_return_object \n";return Task{Handle::from_promise(*this)};}/**返回的就是std::suspend_always,在協(xié)程被創(chuàng)建及真正運(yùn)行前,被調(diào)用*/static std::suspend_always initial_suspend() noexcept {std::cout << "initial_suspend \n";return {}; }//返回awaiter,在協(xié)程最后退出后調(diào)用的接口。static std::suspend_always final_suspend() noexcept { std::cout << "final_suspend \n";return {}; }//返回awaiter,會(huì)在 co_yield v 時(shí)被調(diào)用類(lèi)型就是T,v就是傳入?yún)?shù)valuestd::suspend_always yield_value(T value) noexcept //-4-{current_value = std::move(value);std::cout << "yield_value ";return {};}//會(huì)在 co_return v 時(shí)被調(diào)用,把 co_return 后面跟著的值value作為參數(shù)傳入,這里一般就是把這個(gè)值保存下來(lái),提供給協(xié)程調(diào)用者void return_value(const T& value){std::cout << "return_value call "<< value << "\n";//cout危險(xiǎn)操作,取決于T類(lèi)型,這里為了展示原理current_value = std::move(value);return;}//會(huì)在 co_return v 時(shí)被調(diào)用,無(wú)傳入值,和return_value只選一個(gè),否則會(huì)報(bào)編譯錯(cuò)誤/*void return_void(){std::cout << "return void invoked." << std::endl;}*/// 生成器協(xié)程中不允許 co_await 。void await_transform() = delete;//協(xié)程內(nèi)的代碼拋出了異常,這個(gè)接口會(huì)被調(diào)用static void unhandled_exception() {std::cout << "unhandled_exception ";throw;}std::optional<T> current_value;};using Handle = std::coroutine_handle<promise_type>;//協(xié)程句柄explicit Task(Handle coroutine) : m_coroutine{coroutine}{}Task() = default;~Task() { std::cout << "~Task \n";if (m_coroutine) //自行銷(xiāo)毀{m_coroutine.destroy(); }}Task(const Task&) = delete;Task& operator=(const Task&) = delete;Task(Task&& other) noexcept : m_coroutine{other.m_coroutine}{ other.m_coroutine = {}; }Task& operator=(Task&& other) noexcept {if (this != &other) {m_coroutine = other.m_coroutine;other.m_coroutine = {};}return *this;}// 基于范圍的 for 循環(huán)支持。通過(guò)迭代操作實(shí)現(xiàn)協(xié)程應(yīng)用class Iter {public:void operator++() //-6-{ std::cout << "real resume ";m_coroutine.resume(); //++時(shí),恢復(fù)協(xié)程}const T& operator*() const { return *m_coroutine.promise().current_value; //取值,通過(guò)promise獲取數(shù)據(jù),返回值T} bool operator==(std::default_sentinel_t) const { return !m_coroutine || m_coroutine.done(); //賦值時(shí),協(xié)程執(zhí)行}explicit Iter(Handle coroutine) : m_coroutine{coroutine}{}private:Handle m_coroutine;//協(xié)程句柄};//range應(yīng)用指定的開(kāi)閉區(qū)間Iter begin() {if (m_coroutine) {m_coroutine.resume();} return Iter{m_coroutine};}std::default_sentinel_t end() { return {}; }const T& get_val(){return *m_coroutine.promise().current_value;}
private:Handle m_coroutine; //協(xié)程句柄
};//協(xié)程函數(shù)的返回值為T(mén)ask<T>類(lèi)型,協(xié)程的返回類(lèi)型必須內(nèi)部定義Task<T>::promise_type
template<std::integral T>
Task<T> range(T first, T last)
{T sum = T();while (first < last) //-2-{sum += first;co_yield first++;//協(xié)程會(huì)掛起,返回值;等價(jià)于co_await promise.yield_value(表達(dá)式)。-3-//調(diào)用者resume時(shí),在此處恢復(fù)執(zhí)行std::cout << "co_yield\n";//-7-}co_return sum;//等價(jià)于co_await promise.return_value(表達(dá)式)
};void coroutine_model_test(void)
{auto rs = range(-4, 4);for (int i : rs) //-1-,// for (int i : range(-4, 4)) //-1-,{std::cout << i << " ";//-5-}std::cout << "\n";std::cout << "rs last val = " << rs.get_val() << "\n";
};
//main.cpp
#include "test1.h"int main(int argc, char* argv[])
{coroutine_model_test();return 0;
};
?????? 本案例定義了一個(gè)for循環(huán)的范圍返回函數(shù),該范圍是Task類(lèi)的begin()和end()函數(shù)提供,而函數(shù)引用了內(nèi)置類(lèi)型Iter,Iter在迭代遞增時(shí)(operator++()),會(huì)調(diào)用std::coroutine_handle的resume進(jìn)行協(xié)程恢復(fù)。協(xié)程運(yùn)行到“co_yield first++;”時(shí),就會(huì)通過(guò)yield_value設(shè)置了promise內(nèi)部的緩存值,并返回,而調(diào)用者函數(shù)coroutine_model_test則通過(guò)std::coroutine_handle句柄獲知promise承諾對(duì)象及內(nèi)部值(及遍歷數(shù)值),在遍歷時(shí),每次遞增,本質(zhì)上會(huì)調(diào)用std::coroutine_handle的resume告知協(xié)程恢復(fù)執(zhí)行,而協(xié)程每次進(jìn)行遞增數(shù)值會(huì)寫(xiě)入promise承諾對(duì)象內(nèi)部,如此反復(fù)等同于coroutine_model_test獲得遍歷范圍值。
??????? 協(xié)程最終返回時(shí)“co_return sum;”,傳遞回來(lái)一個(gè)數(shù)值,本質(zhì)上是通過(guò)promise承諾對(duì)象內(nèi)部return_value函數(shù)實(shí)現(xiàn)的。因?yàn)橥ㄟ^(guò)“current_value = std::move(value);”將最后傳遞進(jìn)入的值保存在promise承諾對(duì)象內(nèi)部,因此在調(diào)用函數(shù)內(nèi)通過(guò)get_val就能取得該緩存的值。
const T& Task<T>::get_val(){return *m_coroutine.promise().current_value;}
???????? 上述例子中通過(guò)“-*-”標(biāo)識(shí)了協(xié)程調(diào)用邏輯次序,編譯g++ main.cpp test*.cpp -o test.exe -std=c++20,運(yùn)行測(cè)試:
???????? Task內(nèi)部promise 類(lèi)的工作主要是兩個(gè):
- 從協(xié)程的承諾對(duì)象創(chuàng)建 coroutine_handle,接口是get_return_object。
- 是定義協(xié)程的執(zhí)行流程,主要接口是initial_suspend,final_suspend。
- 是負(fù)責(zé)協(xié)程和調(diào)用者之間的數(shù)據(jù)傳遞,主要接口是 yield_value 和return_value或return_void。
????????通常,promise_type類(lèi)型需要主要實(shí)現(xiàn)這幾個(gè)接口:
??????? 【1】Task<T> get_return_object () 這個(gè)接口要能用 promise 自己的實(shí)例構(gòu)造出一個(gè)協(xié)程的返回值,會(huì)在協(xié)程正在運(yùn)行前進(jìn)行調(diào)用,這個(gè)接口的返回值會(huì)作為協(xié)程的返回值。
??????? 【2】std::suspend_always initial_suspend () 這個(gè)接口會(huì)在協(xié)程被創(chuàng)建(也就是第一次調(diào)用),真正運(yùn)行前,被調(diào)用。在上述這個(gè)例子里,指定返回空類(lèi),指示 await 表達(dá)式始終暫停并且不產(chǎn)生值。
return {};
????????std::suspend_always是一個(gè)結(jié)構(gòu)體,前面已經(jīng)說(shuō)明了std::suspend_never,下來(lái)看看std::suspend_always,它和std::suspend_never幾乎一樣:
/* (C++20 起)
*std::suspend_always,定義于頭文件 <coroutine>
*suspend_always 是空類(lèi),能用于指示 await 表達(dá)式始終暫停并且不產(chǎn)生值。
*/
struct suspend_always;
??????? 該類(lèi)包含幾個(gè)成員函數(shù),用來(lái)判定
成員函數(shù)
/*(C++20) 指示 await 表達(dá)式始終暫停(公開(kāi)成員函數(shù))
*std::suspend_always::await_ready
*始終返回 false ,指示 await 表達(dá)式始終暫停。
*/
constexpr bool await_ready() const noexcept { return false; }/*(C++20) 無(wú)操作(公開(kāi)成員函數(shù))
*std::suspend_always::await_suspend
*不做任何事。
*/
constexpr void await_suspend() const noexcept {}(C++20 起) /*(C++20) 無(wú)操作(公開(kāi)成員函數(shù))
*std::suspend_always::await_resume
*不做任何事。若使用 suspend_always 則 await 表達(dá)式不產(chǎn)生值。
*/
constexpr void await_resume() const noexcept {}
??????? 【3】std::suspend_always yield_value (T v) 這個(gè)接口會(huì)在 co_yield v 時(shí)被調(diào)用,把 co_yield 后面跟著的值 v 做為參數(shù)傳入,這里一般就是把這個(gè)值保存下來(lái),提供給協(xié)程的調(diào)用者,返回值一般是std::suspend_always {}。
??????? 【4】void return_value (T v) 這個(gè)接口會(huì)在 co_return v 時(shí)被調(diào)用,把 co_return 后面跟著的值 v 作為參數(shù)傳入,這里一般就是把這個(gè)值保存下來(lái),提供給協(xié)程調(diào)用者。
??????? 【5】void return_void () 如果 co_return 后面沒(méi)有接任何值,那么就會(huì)調(diào)用這個(gè)接口。return_void 和return_value 只能選擇一個(gè)實(shí)現(xiàn),否則會(huì)報(bào)編譯錯(cuò)誤。
??????? 【6】std::suspend_always final_suspend () 在協(xié)程最后退出后調(diào)用的接口,如果返回 std::suspend_always 。
??????? 【7】協(xié)程結(jié)束后,則需要用戶(hù)自行調(diào)用 coroutine_handle 的 destroy 接口來(lái)釋放協(xié)程相關(guān)的資源。若協(xié)程對(duì)應(yīng)的 handle 就已經(jīng)為空,不能再調(diào)用 destroy 了 (會(huì) coredump)。
??????? 【8】void unhandled_exception () 如果協(xié)程內(nèi)的代碼拋出了異常,那么這個(gè)接口會(huì)被調(diào)用。
????????std::coroutine_handle<promise_type> 是協(xié)程的控制句柄類(lèi),也是協(xié)程函數(shù)返回類(lèi)型的最重要成員,通過(guò)標(biāo)準(zhǔn)庫(kù)里std::coroutine_handle結(jié)構(gòu)體定義,就可以實(shí)現(xiàn)與承諾對(duì)象的交互能力?;謴?fù)協(xié)程、銷(xiāo)毀協(xié)程實(shí)例等都是通過(guò)該句柄實(shí)現(xiàn)。
??????? 2.5 co_await
??????? 一元運(yùn)算符 co_await 暫停協(xié)程并將控制返回給調(diào)用方。它的操作數(shù)是一個(gè)函數(shù)表達(dá)式:
co_await 函數(shù)表達(dá)式
??????? 函數(shù)表達(dá)式,即函數(shù),其返回一個(gè)類(lèi)似于std::suspend_always可等待結(jié)構(gòu)體(awaitable),就是需要像std::suspend_always一樣為該結(jié)構(gòu)體定義await_ready、await_suspend、await_resume函數(shù):
- 協(xié)程函數(shù)resuming_on_new_thread,調(diào)用函數(shù)表達(dá)式fun(switch_to_new_thread),fun返回結(jié)果,就是一個(gè)等待體(這里是awaitable)。
- 開(kāi)始調(diào)用 await_ready()。如果它的結(jié)果按語(yǔ)境轉(zhuǎn)換成 bool 為 false,那么:暫停協(xié)程(以各局部變量和當(dāng)前暫停點(diǎn)填充其協(xié)程狀態(tài)),然后調(diào)用 await_suspend 接口,并將協(xié)程的句柄傳給這個(gè)接口。
- 如果await_ready 返回 true,那么協(xié)程完全不會(huì)被掛起,直接會(huì)去調(diào)用 await_resume () 接口,把這個(gè)接口作為 await 的返回值,繼續(xù)執(zhí)行協(xié)程。
- 調(diào)用 await_suspend(handle),其中 handle 是表示當(dāng)前協(xié)程的協(xié)程句柄。這個(gè)函數(shù)內(nèi)部可以通過(guò)這個(gè)句柄觀察暫停的協(xié)程,而且此函數(shù)負(fù)責(zé)調(diào)度它以在某個(gè)執(zhí)行器上恢復(fù),或?qū)⑵滗N(xiāo)毀(并返回 false 當(dāng)做調(diào)度) ?
- 如果 await_suspend 返回 void,那么立即將控制返回給當(dāng)前協(xié)程的調(diào)用方/恢復(fù)方(此協(xié)程保持暫停),否則如果 await_suspend 返回 bool,那么:
- 值為 true 時(shí)將控制返回給當(dāng)前協(xié)程的調(diào)用方/恢復(fù)方
- 值為 false 時(shí)恢復(fù)當(dāng)前協(xié)程。
- 如果 await_suspend 返回某個(gè)其他協(xié)程的協(xié)程句柄,那么(通過(guò)調(diào)用 handle.resume())恢復(fù)該句柄(注意這可以連鎖進(jìn)行,并最終導(dǎo)致當(dāng)前協(xié)程恢復(fù))
- 如果 await_suspend 拋出異常,那么捕捉該異常,恢復(fù)協(xié)程,并立即重拋異常
- 最后,調(diào)用 await_resume(),它的結(jié)果就是整個(gè) co_await expr 表達(dá)式的結(jié)果。
- 如果協(xié)程在 co_await 表達(dá)式中暫停而在后來(lái)恢復(fù),那么恢復(fù)點(diǎn)處于緊接對(duì) await_resume() 的調(diào)用之前。
????????注意,因?yàn)閰f(xié)程在進(jìn)入 await_suspend() 前已經(jīng)完全暫停,所以該函數(shù)可以自由地在線(xiàn)程間轉(zhuǎn)移協(xié)程柄而無(wú)需額外同步。例如,可以將它放入回調(diào),將它調(diào)度成在異步 I/O 操作完成時(shí)在線(xiàn)程池上運(yùn)行等。此時(shí)因?yàn)楫?dāng)前協(xié)程可能已被恢復(fù),從而執(zhí)行了等待器的析構(gòu)函數(shù),同時(shí)由于 await_suspend() 在當(dāng)前線(xiàn)程上持續(xù)執(zhí)行,await_suspend() 應(yīng)該把 *this 當(dāng)作已被銷(xiāo)毀并且在柄被發(fā)布到其他線(xiàn)程后不再訪(fǎng)問(wèn)它。
????????? 2.6 co_await案例
??????? 下面例子,定義了一個(gè)awaitable,具有定義await_ready、await_suspend、await_resume函數(shù)成員函數(shù),通過(guò)switch_to_new_thread函數(shù)表達(dá)式返回co_await。
//test2.h
#ifndef _TEST_2_H_
#define _TEST_2_H_
void coroutine_wait_test(void);
#endif //_TEST_2_H_
//test2.cpp
#include "test2.h"
#include <coroutine>
#include <iostream>
#include <stdexcept>
#include <thread>auto switch_to_new_thread(std::jthread& out)
{struct awaitable {std::jthread* p_out;//co_await開(kāi)始會(huì)調(diào)用,根據(jù)返回值決定是否掛起協(xié)程bool await_ready() { return false; }//在協(xié)程掛起后會(huì)調(diào)用這個(gè),如果返回true,會(huì)返回調(diào)用者,如果返回false,會(huì)立刻resume協(xié)程void await_suspend(std::coroutine_handle<> h) {std::jthread& out = *p_out;if (out.joinable())throw std::runtime_error("jthread out arg is unull");out = std::jthread([h] { h.resume(); });//新建線(xiàn)程,并協(xié)程恢復(fù)std::cout << "new thread ID:" << out.get_id() << "\n"; //}//在協(xié)程resume的時(shí)候會(huì)調(diào)用這個(gè),這個(gè)的返回值會(huì)作為await的返回值void await_resume() {}};return awaitable{&out};
}struct Task{struct promise_type {Task get_return_object() { return {}; }std::suspend_never initial_suspend() { return {}; }std::suspend_never final_suspend() noexcept { return {}; }void return_void() {}void unhandled_exception() {}};
};Task resuming_on_new_thread(std::jthread& out) {std::cout << "initial,ID:" << std::this_thread::get_id() << "\n";co_await switch_to_new_thread(out);//協(xié)程等待// 等待器在此銷(xiāo)毀std::cout << "final,ID:" << std::this_thread::get_id() << "\n";
}void coroutine_wait_test(void)
{std::jthread out;auto ret = resuming_on_new_thread(out);
};
//main.cpp
#include "test2.h"int main(int argc, char* argv[])
{coroutine_wait_test();return 0;
};
?????? 編譯g++ main.cpp test*.cpp -o test.exe -std=c++20及運(yùn)行程序:
?????????2.7 無(wú)操作協(xié)程
??????? 前面描述協(xié)程支持庫(kù)就提到過(guò)無(wú)操作協(xié)程,相比一般協(xié)程,它體現(xiàn)如此特征:在協(xié)程控制流外不做任何事,在開(kāi)始和恢復(fù)后立即暫停,擁有一種協(xié)程狀態(tài),而銷(xiāo)毀該狀態(tài)為無(wú)操作,若有任何指代它的 std::coroutine_handle 則絕不抵達(dá)其最終暫停點(diǎn)。
無(wú)操作協(xié)程,定義于頭文件 <coroutine>
noop_coroutine (C++20)創(chuàng)建在等待或銷(xiāo)毀時(shí)無(wú)可觀察作用的協(xié)程柄(函數(shù))
noop_coroutine_promise (C++20)用于無(wú)可觀察作用的協(xié)程(類(lèi))
noop_coroutine_handle (C++20)std::coroutine_handle<std::noop_coroutine_promise> ,有意用于指代無(wú)操作協(xié)程
(typedef)
????????std::noop_coroutine_promise是是無(wú)操作協(xié)程的承諾類(lèi)型,本質(zhì)上就是一個(gè)前面講述的空promise_type結(jié)構(gòu)體:
//定義于頭文件 <coroutine>
struct noop_coroutine_promise {};
??????? 而std::noop_coroutine_handle就是std::coroutine_handle句柄以std::noop_coroutine_promise為承諾對(duì)象的特例化,
//定義于頭文件 <coroutine>,(C++20 起)
template< class Promise = void > struct coroutine_handle;template<> struct coroutine_handle<std::noop_coroutine_promise>;
using noop_coroutine_handle = std::coroutine_handle<std::noop_coroutine_promise>;
????????std::noop_coroutine是一個(gè)函數(shù),用來(lái)返回指代無(wú)操作協(xié)程的協(xié)程柄。
/*std::noop_coroutine,定義于頭文件 <coroutine>,(C++20 起)
*返回值指代無(wú)操作協(xié)程的 std::noop_coroutine_handle
*若已有無(wú)操作協(xié)程的協(xié)程狀態(tài),則不指定 noop_coroutine 的后續(xù)調(diào)用是返回先前獲得的協(xié)程柄,
*還是指代新的無(wú)操作協(xié)程的協(xié)程狀態(tài)的協(xié)程柄。
*/
std::noop_coroutine_handle noop_coroutine() noexcept;
????????2.8 無(wú)操作協(xié)程案例
??????? 這是協(xié)程函數(shù)嵌套的例子,該例子里協(xié)程函數(shù)test調(diào)用了協(xié)程函數(shù)get_random,它們的返回值都是Task<int>。協(xié)程返回類(lèi)型內(nèi),定義了一個(gè)可等待體awaiter,在co_await調(diào)用時(shí)開(kāi)始觸發(fā)。另外還為協(xié)程返回類(lèi)型Task定義了承諾類(lèi)型promise_type,及在該承諾類(lèi)型內(nèi)定義了一個(gè)可等待體final_awaiter,它會(huì)在承諾類(lèi)型調(diào)用final_suspend時(shí)構(gòu)建和開(kāi)發(fā)觸發(fā)??傻却wfinal_awaiter的await_suspend函數(shù)在傳遞協(xié)程句柄有效時(shí)直接返回,以恢復(fù)先前的協(xié)程;否則返回 noop_coroutine() ,其恢復(fù)不做任何事。
//test3.h
#ifndef _TEST_3_H_
#define _TEST_3_H_void coroutine_noop_test(void);#endif //_TEST_3_H_
//test3.cpp
#include "test3.h"
#include <coroutine>
#include <utility>
#include <iostream>template<class T>
struct Task {struct promise_type {//承諾類(lèi)型promise_type() : result(T()),previous(std::noop_coroutine()){std::cout << "in Task::promise_type()\n";};auto get_return_object() {std::cout << "in Task::promise_type::get_return_object()\n";return Task(std::coroutine_handle<promise_type>::from_promise(*this));}//返回std::suspend_always{} ,表示await表達(dá)式應(yīng)該始終暫停,不立即執(zhí)行,先掛起std::suspend_always initial_suspend() { std::cout << "in Task::promise_type::initial_suspend()\n";return {}; }//可等待體定義struct final_awaiter {//co_await開(kāi)始會(huì)調(diào)用,根據(jù)返回值決定是否掛起協(xié)程bool await_ready() noexcept(true) { std::cout << "in Task::promise_type::final_awaiter::await_ready()\n";return false; }//在協(xié)程掛起后會(huì)調(diào)用這個(gè),如果返回true,會(huì)返回調(diào)用者,如果返回false,會(huì)立刻resume協(xié)程std::coroutine_handle<> await_suspend(std::coroutine_handle<promise_type> h) noexcept(true){// 在當(dāng)前協(xié)程(以 'h' 指代)執(zhí)行即將結(jié)束時(shí)調(diào)用 final_awaiter::await_suspend 。// 若當(dāng)前協(xié)程被另一協(xié)程經(jīng)由 co_await get_Task() 恢復(fù),則存儲(chǔ)到該協(xié)程的柄// 為 h.promise().previous 。該情況下,返回柄以恢復(fù)先前的協(xié)程。// 否則返回 noop_coroutine() ,其恢復(fù)不做任何事。std::cout << "in Task::promise_type::final_awaiter::await_suspend()\n";std::cout << "T = " << h.promise().result << "\n";//co_return *傳遞的值,不規(guī)范語(yǔ)句,主要為了測(cè)試邏輯展示auto previous = h.promise().previous;if (previous) {return previous;} else {return std::noop_coroutine();}}//在協(xié)程resume的時(shí)候會(huì)調(diào)用這個(gè),這個(gè)的返回值會(huì)作為final_awaiter的返回值void await_resume() noexcept(true) {std::cout << "in Task::promise_type::final_awaiter::await_resume()\n";}};//返回final_awaiter{},Task結(jié)束協(xié)程時(shí),將進(jìn)入promise_type.final_suspend,進(jìn)入final_awaiter等待體執(zhí)行邏輯final_awaiter final_suspend() noexcept { std::cout << "in Task::promise_type::final_suspend()\n";return {}; }void unhandled_exception() { throw; }//會(huì)在 co_return v 時(shí)被調(diào)用,把這個(gè)v值保存下來(lái),提供給協(xié)程調(diào)用者void return_value(T value) { std::cout << "in Task::promise_type::return_value()\n";result += std::move(value); }T result;std::coroutine_handle<> previous;};//Task(std::coroutine_handle<promise_type> h) : coro(h) {//get_return_object函數(shù)內(nèi)調(diào)用std::cout << "in Task()\n";}Task(Task&& t) = delete;~Task() { std::cout << "in ~Task()\n";coro.destroy(); }//可等待體定義struct awaiter {//co_await開(kāi)始會(huì)調(diào)用,根據(jù)返回值決定是否掛起協(xié)程bool await_ready() { std::cout << "in Task::awaiter::await_ready()\n";return false; //掛起}//在協(xié)程掛起后會(huì)調(diào)用這個(gè),如果返回true,會(huì)返回調(diào)用者,如果返回false,會(huì)立刻resume協(xié)程auto await_suspend(std::coroutine_handle<> h) {std::cout << "in Task::awaiter::await_suspend()\n";coro.promise().previous = h;//將Task協(xié)程句柄指向的promise,其內(nèi)部定義std::coroutine_handle<> 特例化句柄return coro;}//在協(xié)程resume的時(shí)候會(huì)調(diào)用這個(gè),這個(gè)的返回值會(huì)作為awaiter的返回值T await_resume() { std::cout << "in Task::awaiter::await_resume()\n";return std::move(coro.promise().result); }std::coroutine_handle<promise_type> coro;};awaiter operator co_await() { //co_await調(diào)用std::cout << "in Task::co_await()\n";return awaiter{coro}; //將Task協(xié)程句柄傳入}T operator()() {std::cout << "in Task::operator()\n";coro.resume();return std::move(coro.promise().result);}
private:std::coroutine_handle<promise_type> coro;//協(xié)程句柄
};//協(xié)程函數(shù),返回Task<int>
Task<int> get_random() {std::cout << "in get_random()\n";co_return 4;
};//協(xié)程函數(shù),返回Task<int>
Task<int> test() {Task<int> v = get_random(); //Task<int> u = get_random();std::cout << "in test()\n";int x = (co_await v + co_await u);//相當(dāng)于調(diào)用Task::co_await()co_return x;
};void coroutine_noop_test(void)
{Task<int> t = test();//int result = t();std::cout << result << '\n';
};
//main.cpp
#include "test3.h"int main(int argc, char* argv[])
{coroutine_noop_test();return 0;
};
??????? 編譯g++ main.cpp test3.cpp -o test.exe -std=c++20,運(yùn)行測(cè)試: