C++20協程解糖 - 調用流程和一些細節


本文的前置知識:你至少要知道其他語言的無棧協程是如何實現的,如C#,python。lua不算,lua實際上是有棧協程(對lua虛擬機有棧)

如果你看到這行文字,說明這篇文章被無恥的盜用了(或者你正在選中文字),請前往 cnblogs.com/pointer-smq 支持原作者,謝謝

編譯時:

  1. 編譯器轉寫每個coroutine,生成一個thunk和一個state_object
    1. thunk為了保持調用的語法,封裝創建state_object和返回值的事情
    2. coroutine本體轉寫為state_object對象,轉寫的樣子可以見上一篇文章。state_object有一個promise成員,類型為 協程返回值::promise_type
  2. 將所有coroutine的調用處都改為調用thunk函數

運行時:

  1. 調用thunk,thunk干這些事
    1. coro = new state_object
    2. coro.promise.get_return_object() 但不着急返回(后文稱此對象為future)
    3. 啟動coro
    4. 返回(2)得到的東西(這里其實有rvo,因此不要求get_return_object的拷貝構造)
  2. coro啟動后的流程
    1. 通過 await promise.initial_suspend() ,協程庫編寫者可以指定協程是否在剛啟動時就立即暫停
      1. 一般給generator用,等到真的在迭代generator的時候,恢復協程執行真正的邏輯,一個簡單的lazy
      2. 也可以給其他異步函數用,等到await的時候才啟動異步邏輯,而不是調用時就啟動
    2. 從(1)恢復后,執行coroutine真正的邏輯
      1. return語句會被轉換為 promise.return_value 或者 promise.return_void ,令另一邊的future拿到協程的返回值
      2. await obj 會暫停協程,並將協程句柄交給obj,等obj通過此句柄再喚醒自己(后面細說)
    3. (2)完成后,執行 await promise.final_suspend() ,協程庫編寫者可以指定協程在執行完用戶邏輯后暫停,來避免一些資源共享的問題
      1. 一般來講generator在這里暫停,因為generator要輕量,所以generator和協程這端的promise之間沒有new出來的共享狀態,generator直接引用協程,因此協程隨generator銷毀,而非執行完后自動銷毀,否則就會出現generator引用了已經銷毀協程的問題
      2. 如果future和promise之間有共享的shared state,那final suspend可以不暫停,promise隨協程銷毀,future仍然持有着shared_state及其中的返回

單獨說一下協程 r = await future 的流程

  1. 檢查 future.await_ready() ,若true,則表明future已經完成,協程不需要暫停(語義上是暫停立即恢復),跳到(3)
  2. 否則通過 future.await_suspend(handle) 將協程句柄交給future
    1. future.await_suspend 的返回值可以是協程,那么調用會恢復這個協程(切過去),這就是前面提到的再await時才啟動的協程的啟動位置
    2. future.await_suspend 返回值也可以是bool,當bool是false的時候不暫停協程,直接跳到(3)
    3. 返回其他值的,忽略並暫停協程
  3. 從(2)中resume之后,調用 r = future.await_resume() ,取出來future中的返回值

一些重點:

  1. 協程端持有promise,調用者持有future,協程通過promise給future傳送結果
  2. promise和future需要新增一些接口以供C++20協程使用
    1. Future<T>::promise_type 指定本future類型對應的promise
    2. promise.initial_suspend / final_suspend
    3. promise.get_return_object() 返回自己對應的future給調用者
    4. promise.return_value / return_void 看情況提供
    5. future.await_ready / await_suspend
    6. future在完成后記得調用 await_suspend 傳進來的 handle.resume()
  3. 協程可以在啟動前和結束后額外暫停兩次,由promise控制

一些細節:

  1. promise.initial_suspendfinal_suspend 通常返回的是C++自帶的awaitable:suspend_alwayssuspend_never
  2. 如果coroutine中出現未處理異常,會調用 promise.unhandled_exception() ,然后直接進入 final_suspend
  3. yield會被轉換成 promise.yield_value
  4. coroutine會在 final_suspend 后自動銷毀,也可以由 coroutine_handle.destroy() 手動銷毀
  5. 所謂 coroutine_handle 就是new出來的state_object指針包一下,沒有引用計數,也沒有禁用拷貝,析構也不會自動delete,注意別泄露和懸掛
  6. promise實際上就是state_object的成員,而state_object的地址就是 coroutine_handle,所以給定一個promise引用,他可以通過取地址增減偏移量的方式,和 coroutine_handle 互相轉換,通過 coroutine_handle::from_promise / coroutine_handle:promise

Snipaste_2020-05-10_15-33-44


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM