The Coroutine in C++ 20 協程之諾
引言
本文接著The Coroutine in C++ 20 協程初探,繼續嘮嘮C++裡面的Coroutine。
在上文中,我們看到如下的reply() coroutine
sync
<
int
>
reply
()
{
std
::
cout
<<
“Started await_answer”
<<
std
::
endl
;
auto
a
=
co_await
read_data
();
std
::
cout
<<
“Data we got is ”
<<
a
<<
std
::
endl
;
auto
v
=
co_await
write_data
();
std
::
cout
<<
“write result is ”
<<
v
<<
std
::
endl
;
co_return
42
;
}
要讓這個函式正確執行,我們需要sync
type,而這個promise_
type提供下面的介面
initial_suspend()
get_
return_object()
final_suspend()
return_value()
return_void
() (如果coroutine最後返回的是void)
yield_value
() (如果coroutine裡面有co_yield)
這個promise_type是我們與coroutine進行互動的媒介,而這些介面則控制著reply coroutine的行為。下面詳細分析——
coroutine:將函式切分
正如Coroutine, 非同步,同步,async, await所講,coroutine是可以被suspend和resume的函式。我們可以形象地理解,藉助coroutine,將函式切分為幾塊,這樣我們可以先執行一塊,suspend這個coroutine,去幹些不相關的活,等條件成熟的時候,比如IO ready了,我們就將這個coroutine resume繼續原來的活。如下圖,左邊灰色的方框是reply() coroutine,右邊橙色背景的是
promise type
。reply() coroutine藍色的方框表示coroutine的程式碼,而黃色的方框則是我們可以將這個coroutine suspend 的位置。
在上圖,reply() coroutine有兩個suspend 位置:
inital_
suspend point
final_
suspend point
initial_
supsend point顧名思義,就是最開始的地方,編譯器會將promise_type的initial_suspend()
(比如下面的程式碼)插入到
inital_
supend point。
auto
initial_suspend
()
{
Trace
t
;
std
::
cout
<<
“Started the coroutine, don‘t stop now!”
<<
std
::
endl
;
return
std
::
experimental
::
suspend_never
{};
}
initial_
suspend()返回std::experimental::suspend_
nerver表示coroutine不要掛起,如果返回always,就是要掛起這個coroutine。
同理,final_
suspend point表示當coroutine進行到最後的時候,要做什麼。在本文的例子裡,編譯器會將下面的final_suspend插入到final_
suspend point
auto
final_suspend
()
noexcept
{
Trace
t
;
std
::
cout
<<
“Finished the coro”
<<
std
::
endl
;
return
std
::
experimental
::
suspend_always
{};
}
這裡我們return always,表示程式執行到最後的時候將這個coroutine掛起。
從上面的講解,我們可以看到,編譯器會在函式特定的位置插入一些介面,將函式轉變成coroutine。而這些介面由promise type提供。
這樣子程式設計師就可以將自己希望的邏輯接入到coroutine對應的位置,從而控制coroutine。推薦你嘗試執行A Simple C++ Coroutine,從而看到其中的效果。
promise type
接下來,讓我們來一起看看神奇的promise type~
從A Simple C++ Coroutine將promise type摘出來,貼在進行更好的參照
class
promise_type
{
T
value
;
promise_type
()
{
Trace
t
;
std
::
cout
<<
“Promise created”
<<
std
::
endl
;
}
~
promise_type
()
{
Trace
t
;
std
::
cout
<<
“Promise died”
<<
std
::
endl
;
}
auto
get_return_object
()
{
Trace
t
;
std
::
cout
<<
“Send back a sync”
<<
std
::
endl
;
return
sync
<
T
>
{
handle_type
::
from_promise
(
*
this
)};
}
auto
initial_suspend
()
{
Trace
t
;
std
::
cout
<<
“Started the coroutine, don’t stop now!”
<<
std
::
endl
;
return
std
::
experimental
::
suspend_never
{};
}
auto
return_value
(
T
v
)
{
Trace
t
;
std
::
cout
<<
“Got an answer of ”
<<
v
<<
std
::
endl
;
value
=
v
;
return
std
::
experimental
::
suspend_never
{};
}
auto
final_suspend
()
noexcept
{
Trace
t
;
std
::
cout
<<
“Finished the coro”
<<
std
::
endl
;
return
std
::
experimental
::
suspend_always
{};
}
void
unhandled_exception
()
{
std
::
exit
(
1
);
}
};
從上圖,我們可以看到,promise type 主要有這幾個介面,它們會被編譯器插入到coroutine特定的位置。所以當我們執行coroutine的時候,執行到特定的位置就會執行這些
介面函式
。
如何可以更好理解promise type?其實我們可以從它的名字下手。它叫promise,說明它實際上是一種允諾,是對未來的希望,所以它負責的是控制coroutine的行為(就像我們對未來的希望會控制我們的行為),負責提供coroutine的結果給caller,因為這是它對caller的諾言。所以除了上面講到的介面外,它實際上儲存了coroutine的
返回值
。而當coroutine完成的時候,我們就可以透過promise獲取對應的結果。
struct
promise_type
{
T
value
;
// 這是我們要儲存的coroutine的完成的結果,也就是返回值
promise_type
()
{
Trace
t
;
std
::
cout
<<
“Promise created”
<<
std
::
endl
;
}
}
上面的程式碼,value成員負責儲存coroutine的結果。當我們在coroutine裡面呼叫
co_return res
的時候,實際上我們將res儲存到了value裡面,如
auto
return_value
(
T
res
)
{
Trace
t
;
std
::
cout
<<
“Got an answer of ”
<<
res
<<
std
::
endl
;
value
=
res
;
return
std
::
experimental
::
suspend_never
{};
}
而coroutine的呼叫者,比如read_data() coroutine 的呼叫者reply() ,就可以獲取儲存的value成員。怎麼獲取呢?在開始呼叫read_data coroutine的時候,呼叫者會透過get_return_object()獲取這個
read_data coroutine
的handle,透過handle就可以獲取read_data courouine的結果了。再舉個簡單的例子,比如main函數里面,變數a儲存的就是從get_return_object返回的handle, 即sync
int
main
()
{
std
::
cout
<<
“Start main()。
\n
”
;
auto
a
=
reply
();
return
a
。
get
();
}
總結,sync<int>, lazy<std::string>是promise type的handle,呼叫者透過他們獲取coroutine的結果。而promise type則負責控制coroutine的行為,在關鍵的位置插入特定的介面,實現控制coroutine的執行。