本文的前置知識:你至少要知道其他語言的無棧協程是如何實現的,如C#,python。lua不算,lua實際上是有棧協程(對lua虛擬機有棧)
如果你看到這行文字,說明這篇文章被無恥的盜用了(或者你正在選中文字),請前往 cnblogs.com/pointer-smq 支持原作者,謝謝
編譯時:
- 編譯器轉寫每個coroutine,生成一個thunk和一個state_object
- thunk為了保持調用的語法,封裝創建state_object和返回值的事情
- coroutine本體轉寫為state_object對象,轉寫的樣子可以見上一篇文章。state_object有一個promise成員,類型為
協程返回值::promise_type
- 將所有coroutine的調用處都改為調用thunk函數
運行時:
- 調用thunk,thunk干這些事
coro = new state_object
- coro.promise.
get_return_object()
但不着急返回(后文稱此對象為future) - 啟動coro
- 返回(2)得到的東西(這里其實有rvo,因此不要求get_return_object的拷貝構造)
- coro啟動后的流程
- 通過
await promise.initial_suspend()
,協程庫編寫者可以指定協程是否在剛啟動時就立即暫停 - 一般給generator用,等到真的在迭代generator的時候,恢復協程執行真正的邏輯,一個簡單的lazy
- 也可以給其他異步函數用,等到await的時候才啟動異步邏輯,而不是調用時就啟動
- 從(1)恢復后,執行coroutine真正的邏輯
- return語句會被轉換為
promise.return_value
或者promise.return_void
,令另一邊的future拿到協程的返回值 await obj
會暫停協程,並將協程句柄交給obj,等obj通過此句柄再喚醒自己(后面細說)- (2)完成后,執行
await promise.final_suspend()
,協程庫編寫者可以指定協程在執行完用戶邏輯后暫停,來避免一些資源共享的問題 - 一般來講generator在這里暫停,因為generator要輕量,所以generator和協程這端的promise之間沒有new出來的共享狀態,generator直接引用協程,因此協程隨generator銷毀,而非執行完后自動銷毀,否則就會出現generator引用了已經銷毀協程的問題
- 如果future和promise之間有共享的shared state,那final suspend可以不暫停,promise隨協程銷毀,future仍然持有着shared_state及其中的返回
單獨說一下協程 r = await future
的流程
- 檢查
future.await_ready()
,若true,則表明future已經完成,協程不需要暫停(語義上是暫停立即恢復),跳到(3) - 否則通過
future.await_suspend(handle)
將協程句柄交給future future.await_suspend
的返回值可以是協程,那么調用會恢復這個協程(切過去),這就是前面提到的再await時才啟動的協程的啟動位置future.await_suspend
返回值也可以是bool,當bool是false的時候不暫停協程,直接跳到(3)- 返回其他值的,忽略並暫停協程
- 從(2)中resume之后,調用
r = future.await_resume()
,取出來future中的返回值
一些重點:
- 協程端持有promise,調用者持有future,協程通過promise給future傳送結果
- promise和future需要新增一些接口以供C++20協程使用
Future<T>::promise_type
指定本future類型對應的promisepromise.initial_suspend
/final_suspend
promise.get_return_object()
返回自己對應的future給調用者promise.return_value
/return_void
看情況提供future.await_ready
/await_suspend
- future在完成后記得調用
await_suspend
傳進來的handle.resume()
- 協程可以在啟動前和結束后額外暫停兩次,由promise控制
一些細節:
promise.initial_suspend
和final_suspend
通常返回的是C++自帶的awaitable:suspend_always
和suspend_never
- 如果coroutine中出現未處理異常,會調用
promise.unhandled_exception()
,然后直接進入final_suspend
- yield會被轉換成
promise.yield_value
- coroutine會在
final_suspend
后自動銷毀,也可以由coroutine_handle.destroy()
手動銷毀 - 所謂
coroutine_handle
就是new出來的state_object指針包一下,沒有引用計數,也沒有禁用拷貝,析構也不會自動delete,注意別泄露和懸掛 - promise實際上就是state_object的成員,而state_object的地址就是
coroutine_handle
,所以給定一個promise引用,他可以通過取地址增減偏移量的方式,和coroutine_handle
互相轉換,通過coroutine_handle::from_promise
/coroutine_handle:promise