花了一兩周的時間后,我想寫寫 C++20 協(xié)程的基本用法,因?yàn)?C++ 的協(xié)程讓我感到很奇怪,寫一個協(xié)程程序十分費(fèi)勁。讓我們拋去復(fù)雜的東西,來看看寫一個 C++ 協(xié)程需要哪些東西。
編譯器支持
由于 C++ 20 還沒被所有編譯器完全支持,首先需要確保你的編譯器實(shí)現(xiàn)了 Coroutines,可以通過下面的網(wǎng)站查看編譯器支持情況:https://en.cppreference.com/w/cpp/compiler_support#cpp20
值得一提,我使用的 MacOS 自帶的 Apple Clang 對 C++20 支持很弱,我選擇通過 Homebrew 安裝最新版的 GNU GCC (10 以上版本)來編譯。
我使用的 GNU GCC 10.2 版本編譯指令:
g++ -fcoroutines -std=c++20
Clang 支持不夠好,不推薦使用。Clang 可以使用如下命令編譯:
clang++ -std=c++20 -stdlib=libc++ -fcoroutines-ts
不推薦 Clang 還有一個理由:使用 Clang 需要 include 頭文件 《experimental/coroutine》 而不是 《coroutine》。此外,一些類型被命名為 std:xxx 而不是 std:xxx。
以下示例代碼只支持 GNU GCC 版本的編譯器。
C++ 協(xié)程簡介
在正式開始之前,我們先要理解 C++20 中協(xié)程使用的一些術(shù)語。
首先,什么是協(xié)程?
協(xié)程就是一個可以掛起(suspend)和恢復(fù)(resume)的函數(shù)(但無論如何不能是 main 函數(shù))。你可以暫停協(xié)程的執(zhí)行,去做其他事情,然后在適當(dāng)?shù)臅r候恢復(fù)到暫停的位置繼續(xù)執(zhí)行。協(xié)程讓我們使用同步方式寫異步代碼。
怎么掛起協(xié)程呢?C++ 提供了三個方法:co_await, co_yield 和 co_return。
順便說一句:coroutine 不是并行(parallelism),和 Go 語言的 goroutine 不一樣!
與你之前接觸到的協(xié)程完全不同,一個 C++ 協(xié)程一般長這樣:
這奇怪的協(xié)程代碼涉及了 C++ 協(xié)程很重要的三個概念:
promise_type
Awaitable
std::coroutine_handle《》
在寫 C++20 的協(xié)程之前,我們必須需要先了解三個概念,可以用這三張圖來形容這三個概念:
Promise
C++ 協(xié)程的返回類型必須是 promise_type,promise_type 是一個 interface,你可以用它來控制協(xié)程,在協(xié)程的生命周期中注入自定義行為:
get_return_object:控制協(xié)程的返回對象
initial_suspend:在協(xié)程開始的時候掛起
final_suspend:在協(xié)程結(jié)束的時候掛起
協(xié)程的生命周期如下,用戶自定義的函數(shù) 《function-body》 被包裹在下面的偽代碼中(來源:http://eel.is/c++draft/dcl.fct.def.coroutine#5):
可以看到,initial_suspend 會在進(jìn)入?yún)f(xié)程(也就是函數(shù))之前執(zhí)行,final_suspend 會在協(xié)程返回之前執(zhí)行。
如果 final_suspend 真的掛起了協(xié)程,那么作為協(xié)程的調(diào)用者,你需要手動的調(diào)用 destroy 來釋放協(xié)程;如果 final_suspend 沒有掛起協(xié)程,那么協(xié)程將自動銷毀。先記住這句話,在后面還會提到。
除此之外,Promise 還有一些其它責(zé)任:
return_void()/return_value()/yield_value() 方法: 用來控制 co_return 和 co_yield的行為;
unhandled_exception() 處理異常
創(chuàng)建和銷毀協(xié)程的 stackframe
處理 stackframe 創(chuàng)建可能發(fā)生的異常
stackframe :函數(shù)運(yùn)行時占用的內(nèi)存空間,是棧上的數(shù)據(jù)集合,它包括:
Local variables
Saved copies of registers modified by subprograms that could need restoration
Argument parameters
Return address
Awaitable
第二個概念是 Awaitable,Awaitable 負(fù)責(zé)管理協(xié)程掛起時的行為。
一個 Awaitable 對象可以成為 co_await 調(diào)用的對象。Awaitable 擁有以下方法:
await_ready():是否要掛起,如果返回 true,那么 co_await 就不會掛起函數(shù);
await_resume():co_await 的返回值,通常返回空;
await_suspend():協(xié)程掛起時的行為;
可以在 await_suspend 中實(shí)現(xiàn) await_ready 的效果,例如直接不掛起當(dāng)前的協(xié)程,但在調(diào)用 await_suspend 之前,編譯器必須將所有狀態(tài)捆綁到協(xié)程的 stackframe 中,這會更耗時。
有時候我們的協(xié)程并不需要自定義復(fù)雜的行為,C++ 提供了兩個默認(rèn)的 Awaitable:
suspend_always::await_ready() 總是返回 false,而 suspend_always::await_ready() 總是返回 true。其他的方法都是空的,沒有任何作用。
如果沒有其它多余的行為,我們可以在函數(shù)中直接調(diào)用 co_await std::suspend_always{} 來掛起一個函數(shù)。
Coroutine Handle
co_await 掛起函數(shù),并創(chuàng)建了一個可調(diào)用對象,這個對象可以用來恢復(fù)Hanns乎的執(zhí)行。這個可調(diào)用對象的類型就是 std::coroutine_handle《》,最常用的兩個方法是:
handle.resume():恢復(fù)協(xié)程的執(zhí)行;
handle.destroy():銷毀協(xié)程;
Coroutine Handle 很像指針,我們可以復(fù)制它,但析構(gòu)函數(shù)不會釋放相關(guān)狀態(tài)的內(nèi)存。為了避免內(nèi)存泄漏,一般要調(diào)用 handle.destroy() 來釋放(盡管在某些情況下,協(xié)程會在完成后自行銷毀——前文有提到)。同樣像指針一樣,一旦銷毀了一個 Coroutine Handle ,指向同一個協(xié)程的另一個 Coroutine Handle 將指向垃圾,并在調(diào)用時表現(xiàn)出未定義行為。
學(xué)習(xí)更復(fù)雜的用法之前,我們先看下示例。
示例
這個簡短的示例展示了 C++ 實(shí)現(xiàn)協(xié)程 “Hello world” 程序。我們執(zhí)行完 “Hello ” 后掛起函數(shù),又在執(zhí)行 handle.resume() 后恢復(fù)函數(shù)的運(yùn)行。
非常簡單,不再過多解釋。
co_yield
C++ 協(xié)程與一個 Promise 交互之所以如此笨拙,有一個特殊原因就是為了 co_yield。
如果 promise 是當(dāng)前協(xié)程的 Promise 對象,那么執(zhí)行:
co_yield 《expression》;
相當(dāng)于執(zhí)行了:
co_await promise.yield_value(《expression》);
所以,需要在 promise_type 中添加一個 yield_value 方法。上面的例子可以改為:
可以用 co_yield 實(shí)現(xiàn) Python 中的生成器,參考:https://lewissbaker.github.io/2018/09/05/understanding-the-promise-type
co_return
執(zhí)行 co_return 語句時:
co_return 《expression》;
相當(dāng)于執(zhí)行了:
co_return promise.return_value(《expression》); goto end;
下面再給出示例加上 co_return 的版本:
復(fù)雜一些
到此, Awaitable 和 Coroutine Handle 好像還沒有發(fā)揮什么作用,我寫的示例程序都非常簡單。
如果我們想在協(xié)程掛起的時候,做更多的動作,一般將 Coroutine Handle 傳到 Awaitable 的 await_suspend() 中,用一個官網(wǎng)的例子展示一下:
小結(jié)
本文簡單介紹了 C++ 協(xié)程,希望下次你寫 C++ 協(xié)程的時候,首先想到這三個東西:
我本人也不是編程語言專家,對于 C++ 協(xié)程總覺得有些繁瑣、怪異,或許是我并不清楚 C++ 在原有情況下支持協(xié)程的困難,但我依然覺得 C++ 團(tuán)隊(duì)可以做得更好。
我還需要花時間弄明白到底該如何在項(xiàng)目中使用這臃腫的協(xié)程。
不過,可以預(yù)見到的是,我們會在越來越多的 C++ 項(xiàng)目中看到協(xié)程的身影。比如 facebook folly 就已經(jīng)實(shí)現(xiàn)了一個實(shí)驗(yàn)階段的協(xié)程框架
編輯:lyn
-
C++
+關(guān)注
關(guān)注
22文章
2114瀏覽量
73860 -
代碼
+關(guān)注
關(guān)注
30文章
4828瀏覽量
69063 -
編譯器
+關(guān)注
關(guān)注
1文章
1642瀏覽量
49292
原文標(biāo)題:如何編寫 C++ 20 協(xié)程(Coroutines)
文章出處:【微信號:LinuxHub,微信公眾號:Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論