C++20 協程
本文主要來源於
https://lewissbaker.github.io/2017/09/25/coroutine-theory
https://blog.panicsoftware.com/coroutines-introduction/
https://kirit.com/How%20C%2B%2B%20coroutines%20work/My%20first%20coroutine
https://en.cppreference.com/w/cpp/language/coroutines
協程
A coroutine is a generalization of a function that allows the function to be suspended and then later resumed. It generalizes the operations of a function by separating out some of the steps performed in the Call and Return operations into three extra operations: Suspend, Resume and Destroy.
Since coroutines can be suspended without destroying the activation frame, we can no longer guarantee that activation frame lifetimes will be strictly nested. This means that activation frames cannot in general be allocated using a stack data-structure and so may need to be stored on the heap instead.
可以說協程是一種支持Suspend與Resume的函數,也因此不能用一般函數的棧來實現。
stackful vs stackless 協程區別
stackful : create a separate stack for each coroutine, which can be used to process function calls. If there are several coroutine, several independent stack will be created.
stackless: no need to allocate whole the stack, less memory consuming. Can suspend themselves from the top level
協程的實現可以有獨立的棧或者堆實現兩種, C++中的為后者。后者只能在頂層suspend.
1) 每個協程都有獨立於函數stack的獨立棧幀,調用時候切換棧指針。
2) 把協程的空間分配在heap上
一種協程大致實現(以下皆指stackless coroutine)
1) 一個普通函數f,ESP(寄存器中的棧位置)和EBP(寄存器中的棧基址位置)都指向當前棧對應位置
2)調用協程x,按類似一般函數一樣分配棧空間。同時在堆上分配空間,並把棧中的內容拷貝。
3)協程調用普通函數g,分配一般函數的棧幀。之前協程棧會存儲協程堆空間位置
4)g函數返回,結果值保存在堆空間
5)協程x返回函數f, 堆空間中保持了協程的狀態以及RP(resuming pointer),即下次繼續執行點。函數f保存了協程的指針
6) 但一函數h通過handle繼續執行協程時,仍會分配棧空間,滿足傳參。主要數據為堆空間的值。
協程的大致數據結構
空間一般對應着數據,如上述過程所述,需要保存的數據有協程的參數,內部變量,暫停點?,promise等。
其中promise是C++對應協程規范的一種數據類型,里面有多個成員函數。通過它們,用戶可以自定義協程的行為,如何時暫停、返回等。
get_return_object // to create return object initial_suspend // entering the coroutine body return_value // called when co_return called return_void // called before the end of coroutine body yield_value // called when co_yield called final_suspend // called when coroutine ends unhandled_exception // handle exception
以下是一個普通函數
int function(int a, int b) {}
由於函數與被調用函數之間的傳遞或交流主要是通過返回值實現的,函數與協程也不例外。函數至少需要知道協助的指針,下次才能再次執行。
因此,C++中協程的返回類型必須能夠包含promise類型,協程指針等。
協程返回類型與協程函數
通常表現來說,一個包含co_return, co_yield, co_await api的函數是個協程。返回類型的定義也和使用了哪個api相關。
co_return: 結束協程返回調用函數
//vs2015 #include <iostream> #include <experimental/coroutine> using namespace std; template<class T> struct test { // inner types struct promise_type; using handle_type = std::experimental::coroutine_handle<promise_type>; //type alias // functions test(handle_type h):handle(h) { cout << "# Created a Test object\n"; } test(const test & s) = delete; test & operator=(const test &) = delete; test(test && s) :handle(s.handle) { s.handle = nullptr; } test& operator=(test && s) { handle = s.handle; s.handle = nullptr; return *this; } ~test() { cout << "#Test gone\n"; if (handle) handle.destroy(); } T get() { cout << "# Got return value\n"; if (!(this->handle.done())) { handle.resume(); //resume return handle.promise().value; } } struct promise_type { promise_type() { cout << "@ promise_type created\n"; } ~promise_type() { cout << "@ promise_type died\n"; } auto get_return_object() //get return object { cout << "@ get_return_object called\n"; return test<T>{handle_type::from_promise(*this)};// pass handle to create "return object" } auto initial_suspend() // called before run coroutine body { cout << "@ initial_suspend is called\n"; // return std::experimental::suspend_never{}; // dont suspend it return std::experimental::suspend_always{}; } auto return_value(T v) // called when there is co_return expression { cout << "@ return_value is called\n"; value = v; return std::experimental::suspend_never{}; // dont suspend it //return std::experimental::suspend_always{}; } auto final_suspend() // called at the end of coroutine body { cout << "@ final_suspend is called\n"; return std::experimental::suspend_always{}; } void unhandled_exception() //exception handler { std::exit(1); } // data T value; }; // member variables handle_type handle; }; test<int> coroutine01() { std::cout << "start coroutine01\n"; co_return 1; co_return 2; // will never reach here } int main() { { auto a = coroutine01(); cout << "created a corutine, try to get a value\n"; int an = a.get(); cout << "value is " << an << endl; an = a.get(); cout << "value is " << an << endl; } return 0; }
上述定義個一個test<int>作為返回類型,並且在該類型中定義了promise_type以及其成員函數。編譯時,編譯器會依據這些生產能夠實現協程的執行指令。
大致執行流程可以查看log,第一步是調用get_return_object (所以我們在這個函數實現中要創建返回對象),協程進入initial_suspend->協程函數體->final_suspend協程完全結束。 函數體中遇到co_return 則調用return_value。
在initial_suspend和final_suspend函數中可以通過return true or false來決定是否暫停。
co_yield:返回一個序列的值
#include <iostream> #include <experimental/coroutine> using namespace std; struct test { // inner types struct promise_type; using handle_type = std::experimental::coroutine_handle<promise_type>; //type alias // functions test(handle_type h) :handle(h) { cout << "# Created a Test object\n"; } test(const test & s) = delete; test & operator=(const test &) = delete; test(test && s) :handle(s.handle) { s.handle = nullptr; } test& operator=(test && s) { handle = s.handle; s.handle = nullptr; return *this; } ~test() { cout << "#Test gone\n"; if (handle) handle.destroy(); } int current_value() { return handle.promise().value; } bool move_next() { handle.resume(); return !handle.done(); } struct promise_type { promise_type() { cout << "@ promise_type created\n"; } ~promise_type() { cout << "@ promise_type died\n"; } auto get_return_object() //get return object { cout << "@ get_return_object called\n"; return test{handle_type::from_promise(*this)};// pass handle to create "return object" } auto initial_suspend() // called before run coroutine body { cout << "@ initial_suspend is called\n"; return std::experimental::suspend_never{}; // dont suspend it //return std::experimental::suspend_always{}; } auto return_void() // called when just before final_suspend, conflict with return_value { cout << "@ return_void is called\n"; return std::experimental::suspend_never{}; // dont suspend it //return std::experimental::suspend_always{}; } auto yield_value(int t) // called by co_yield() { std::cout << "yield_value called\n"; value = t; return std::experimental::suspend_always{}; } auto final_suspend() // called at the end of coroutine body { cout << "@ final_suspend is called\n"; return std::experimental::suspend_always{}; } void unhandled_exception() //exception handler { std::exit(1); } // data int value; }; // member variables handle_type handle; }; test coroutine01(int count) { std::cout << "start coroutine01\n"; for(int i =0; i<count; i++ ) co_yield i*2; } int main() { { auto a = coroutine01(4); cout << "created a corutine, try to get a value\n"; do { cout << "get value " << a.current_value() << endl; } while (a.move_next()); } return 0; }
這個函數是co_yield的例子,主要區別是要在promise定義yield_value函數,而且我們定義一個協程指針(通過promise來實例化)以便我們可以多次調用協程。
通過handle.done來判斷協程是否結束
co_await expression
Expression should can be convert to awaitor
An Awaiter type is a type that implements the three special methods that are called as part of a co_await expression: await_ready, await_suspend and await_resume.
Suspend the coroutine when the target is not ready, while awaiting completion of the computation represented by the operand expression
If the promise type, P, has a member named await_transform then <expr> is first passed into a call to promise.await_transform(<expr>) to obtain the Awaitable value
依據expression的值決定是否暫停協程返回調用函數,或者繼續執行。
其中expression需要能夠轉化成Awaitable類型。所謂的awaitable類型是指一種包含await_ready, await_suspend and await_resume 三個函數的數據結構。
具體怎么從表達式轉化成awaitable類型如下:
其中依據promise類型中是否有await_transform和awaitable中是否有operator co_await操作符定義, 從表達式到awaitor的過程也不一樣。
awaitor(可等待對象)是awaitable類型的實例。co_await expression 等效於 co_await awaitor, 進而調用awaitor的各個成員函數來決定co_await的結果。
如上圖,co_await awaitor 等效於 awaitor->await_ready() , awaitor->await_suspend() ...
其中根據await_suspend的返回類型可以是void bool or handle.
#include <iostream> #include <experimental/coroutine> using namespace std; template<class T> struct test { // inner types struct promise_type; using handle_type = std::experimental::coroutine_handle<promise_type>; //type alias // functions test(handle_type h) :handle(h) { cout << "# Created a Test object\n"; } test(const test & s) = delete; test & operator=(const test &) = delete; test(test && s) :handle(s.handle) { s.handle = nullptr; } test& operator=(test && s) { handle = s.handle; s.handle = nullptr; return *this; } ~test() { cout << "#Test gone\n"; if (handle) handle.destroy(); } T get() { cout << "# Got return value\n"; if (!(this->handle.done())) { handle.resume(); //resume return handle.promise().value; } } struct promise_type { promise_type() { cout << "@ promise_type created\n"; } ~promise_type() { cout << "@ promise_type died\n"; } auto get_return_object() //get return object { cout << "@ get_return_object called\n"; return test<T>{handle_type::from_promise(*this)};// pass handle to create "return object" } auto initial_suspend() // called before run coroutine body { cout << "@ initial_suspend is called\n"; // return std::experimental::suspend_never{}; // dont suspend it return std::experimental::suspend_always{}; } auto yield_value(int t) // called by co_yield() { std::cout << "yield_value called\n"; value = t; return std::experimental::suspend_always{}; } auto final_suspend() // called at the end of coroutine body { cout << "@ final_suspend is called\n"; return std::experimental::suspend_always{}; } void unhandled_exception() //exception handler { std::exit(1); } //T await_transform() {} // data T value; }; // member variables handle_type handle; }; struct AwaiableObj { int a; AwaiableObj() :a(0) {} bool await_ready() { cout << "@@ await_ready called\n"; return true; } auto await_suspend(std::experimental::coroutine_handle<> awaiting_handle) { cout << "@@ await_suspend called\n"; // return ; // return true; return false; // return awaiting_handle; } auto await_resume() { cout << "@@ await_resume called\n"; return a++; } }; test<int> await_routine() { auto a = AwaiableObj{}; for (int i = 0; i < 5; i++) { auto v = co_await a; co_yield v; } } int main() { { auto a = await_routine(); auto b = a.get(); cout << "value is " << b << endl; b = a.get(); cout << "value is " << b << endl; b = a.get(); cout << "value is " << b << endl; b = a.get(); cout << "value is " << b << endl; b = a.get(); cout << "value is " << b << endl; } return 0; }
推薦awaitable類型定義在promise 類型之內