您當前的位置:首頁 > 書法

The Coroutine in C++ 20 協程之諾

作者:由 CrackingOysters 發表于 書法時間:2020-09-13

引言

本文接著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包含一個promise_

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 的位置。

The Coroutine in C++ 20 協程之諾

在上圖,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

);

}

};

The Coroutine in C++ 20 協程之諾

從上圖,我們可以看到,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。我們可以透過a。get()獲取promise裡面的value。

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的執行。

標簽: Coroutine  std  suspend  promise  type