微信營(yíng)銷(xiāo)和網(wǎng)站建設(shè)自媒體
文章目錄
- 協(xié)程切換方案
- 協(xié)程庫(kù)的完善程度
- 協(xié)程棧方案
- 協(xié)程調(diào)度實(shí)現(xiàn)
- 有棧協(xié)程與無(wú)棧協(xié)程
- 對(duì)稱(chēng)協(xié)程與非對(duì)稱(chēng)協(xié)程
協(xié)程切換方案
具體使用和解析看棧切換那個(gè)博客
-
使用
setjump
、longjump
c語(yǔ)言提供的方案
可參考:libmill
-
使用操作系統(tǒng)提供的api:
ucontext
、fiber
這種方式是最安全可靠的,但是性能比較差。
可參考:libtask
-
自己寫(xiě)匯編碼實(shí)現(xiàn)
這種方式的性能可以很好,但是不同系統(tǒng)、甚至不同版本的linux都需要不同的匯編碼,兼容性奇差無(wú)比。
可參考:libco
-
使用
boost
coroutine
、context
等性能很好,boost
也幫忙處理了各種平臺(tái)架構(gòu)的兼容性問(wèn)題,不過(guò)需要依賴boost框架。
可參考:libgo
協(xié)程庫(kù)的完善程度
-
API級(jí)
實(shí)現(xiàn)協(xié)程上下文切換api,或添加一些便于使用的封裝。
如:boost.context
,boost.coroutine
問(wèn)題:
沒(méi)有協(xié)程調(diào)度
-
玩具級(jí)
實(shí)現(xiàn)了協(xié)程調(diào)度,無(wú)需用戶手動(dòng)處理協(xié)程上下文切換。
如:libmill問(wèn)題:
沒(méi)有HOOK
,只實(shí)現(xiàn)了一套網(wǎng)絡(luò)io相關(guān)函數(shù),這也意味著涉及網(wǎng)絡(luò)的第三方庫(kù)全部不可用了。 -
工業(yè)級(jí)
以部分正確的方式HOOK了網(wǎng)絡(luò)io相關(guān)的syscall,可以少改甚至不改代碼的兼容大多數(shù)第三方庫(kù)。
如:libco問(wèn)題:
沒(méi)有完整生態(tài)
,協(xié)程間通訊、協(xié)程同步、調(diào)試等機(jī)制不夠完善。未能完全模擬syscall的行為,只能兼容行為符合預(yù)想的同步模型的第三方庫(kù),只能覆蓋一部分的第三方庫(kù)。 -
框架級(jí)
以100%行為模擬的方式HOOK了網(wǎng)絡(luò)io相關(guān)的syscall,可以完全不改代碼兼容大多數(shù)第三方庫(kù)。
如:libgo問(wèn)題:由于C++的靈活性,
用戶行為是不受限的
,所以依然存在幾個(gè)邊邊角角的難點(diǎn)需要開(kāi)發(fā)者注意:沒(méi)有g(shù)c,TLS的問(wèn)題,用戶不按套路出牌、把邏輯代碼run在協(xié)程之外,粗粒度的線程鎖等等。 -
語(yǔ)言級(jí)
語(yǔ)言級(jí)的協(xié)程實(shí)現(xiàn)
如:golang開(kāi)發(fā)者的一切行為都是
受限行為
,可以實(shí)現(xiàn)無(wú)死角的完善的協(xié)程。
c++20也支持協(xié)程了
協(xié)程棧方案
-
靜態(tài)棧
棧大小固定,有大小難以權(quán)衡
的問(wèn)題。
設(shè)置大了,會(huì)造成浪費(fèi)。
設(shè)置小了,會(huì)有棧溢出問(wèn)題。 -
分段棧
GCC支持一種允許棧內(nèi)存不連續(xù)的編譯參數(shù),實(shí)現(xiàn)原理是在每個(gè)函數(shù)調(diào)用開(kāi)頭都插入一段棧內(nèi)存檢測(cè)的代碼,如果棧內(nèi)存不夠用了就申請(qǐng)一塊新的內(nèi)存,作為棧內(nèi)存的延續(xù)。
但是第三方庫(kù)沒(méi)有使用
這種方式來(lái)編譯,那就無(wú)法在其中檢測(cè)棧內(nèi)存是否需要擴(kuò)展,棧溢出的風(fēng)險(xiǎn)很大。 -
拷貝棧
每次檢測(cè)到棧內(nèi)存不夠用時(shí),申請(qǐng)一塊更大的新內(nèi)存,將現(xiàn)有的棧內(nèi)存copy過(guò)去,就像std::vector那樣擴(kuò)展內(nèi)存。
但C/C++是有指針的,棧內(nèi)存的copy會(huì)導(dǎo)致指向其內(nèi)存地址的指針失效
;又因?yàn)槠渲羔樀撵`活性,修改對(duì)應(yīng)的指針成為了一種幾乎不可能實(shí)現(xiàn)的事情。 -
共享?xiàng)?#xff08;libco)
申請(qǐng)一塊大內(nèi)存作為共享?xiàng)?#xff08;比如8MB),每次協(xié)程掛起時(shí)計(jì)算協(xié)程棧真正使用的內(nèi)存,copy到私有棧中;喚醒協(xié)程時(shí),把協(xié)程私有棧的內(nèi)存copy到共享?xiàng)V?#xff0c;這樣每次只需保存真正使用到的棧內(nèi)存量即可。
這種方案極大程度上避免了內(nèi)存的浪費(fèi),做到了用多少占多少,同等內(nèi)存條件下,可以啟動(dòng)的協(xié)程數(shù)量更多,但是協(xié)程切換慢
,還有引用失效
問(wèn)題。 -
虛擬內(nèi)存棧(libgo)
申請(qǐng)的內(nèi)存并不會(huì)立即被映射成物理內(nèi)存,而是僅管理于虛擬內(nèi)存中,真正對(duì)其讀寫(xiě)時(shí)才會(huì)觸發(fā)缺頁(yè)中斷,分配物理內(nèi)存
;而且基本上是按頁(yè)遞增分配。
協(xié)程調(diào)度實(shí)現(xiàn)
-
棧式調(diào)度(libco)
棧式調(diào)度是典型的不公平調(diào)度
。協(xié)程隊(duì)列是一個(gè)棧式的結(jié)構(gòu),每次創(chuàng)建的協(xié)程都置于棧頂,并且會(huì)立即暫停當(dāng)前協(xié)程并切換至子協(xié)程中運(yùn)行,子協(xié)程運(yùn)行結(jié)束(或其他原因?qū)е虑袚Q出來(lái))后,繼續(xù)切換回來(lái)執(zhí)行父協(xié)程;越是處于棧底部的協(xié)程(越早創(chuàng)建的協(xié)程),被調(diào)度到的機(jī)會(huì)就越少。 -
星切調(diào)度(libgo)
調(diào)度線程 -> 協(xié)程A -> 調(diào)度線程 -> 協(xié)程B -> 調(diào)度線程 -> …
調(diào)度線程居中,協(xié)程在周?chē)?#xff0c;調(diào)度順序圖看起來(lái)就像是星星一樣,稱(chēng)為星切。
將當(dāng)前可調(diào)度的協(xié)程組織成先進(jìn)先出的隊(duì)列(runnable list),順序pop出來(lái)做調(diào)度;新創(chuàng)建的協(xié)程排入隊(duì)尾,調(diào)度一次后如果狀態(tài)依然是可調(diào)度(runnable)的協(xié)程則排入隊(duì)尾,調(diào)度一次后如果狀態(tài)變?yōu)樽枞?#xff0c;那阻塞事件觸發(fā)后也一樣排入隊(duì)尾,是為公平調(diào)度
。 -
環(huán)切調(diào)度
調(diào)度線程 -> 協(xié)程A -> 協(xié)程B -> 協(xié)程C -> 調(diào)度線程 -> …
調(diào)度線程居中,協(xié)程在周?chē)?#xff0c;調(diào)度順序圖看起來(lái)呈環(huán)狀,稱(chēng)為環(huán)切。
為了突破傳統(tǒng)協(xié)程庫(kù)僅用來(lái)處理I/O密集型業(yè)務(wù)的局限,也能適用于CPU密集型業(yè)務(wù)
,可充當(dāng)并行編程庫(kù)來(lái)使用。
有棧協(xié)程與無(wú)棧協(xié)程
所謂的有棧,無(wú)棧并不是說(shuō)這個(gè)協(xié)程運(yùn)行的時(shí)候有沒(méi)有棧。
有棧協(xié)程是真的給你開(kāi)了一個(gè)棧(如golang),主流的無(wú)棧協(xié)程方案(例如C++,Rust等),是把一個(gè)協(xié)程函數(shù)編譯成狀態(tài)機(jī)的邏輯,然后用一塊臨時(shí)分配的堆內(nèi)存去保存這個(gè)函數(shù)里的變量和協(xié)程狀態(tài)機(jī)以及上下文等內(nèi)容。
無(wú)棧不管從效率,內(nèi)存占用看當(dāng)然是更優(yōu)的方案,但是無(wú)棧需要編譯器支持
,有棧只需要編寫(xiě)同一套上下文切換的代碼。移植到有棧協(xié)程上比無(wú)棧也要相對(duì)簡(jiǎn)單,現(xiàn)在主流的無(wú)棧協(xié)程基本都需要進(jìn)行侵入式的修改
。
使用上最大的區(qū)別就是協(xié)程是否可以在其任意嵌套函數(shù)中被掛起,有棧協(xié)程是可以的,而無(wú)棧協(xié)程則不可以。
有棧協(xié)程:
- 每個(gè)協(xié)程有單獨(dú)的上下文,可以在任意的嵌套函數(shù)中任何地方掛起此協(xié)程
- 不需要編譯器做語(yǔ)法支持,通過(guò)匯編指令即可實(shí)現(xiàn)
- 需要提前分配一定大小的堆內(nèi)存保存每個(gè)協(xié)程上下文,所以會(huì)出現(xiàn)內(nèi)存浪費(fèi)或者棧溢出
- 上下文拷貝和切換成本高,性能低于無(wú)棧協(xié)程
無(wú)棧協(xié)程:
- 不需要為每個(gè)協(xié)程保存單獨(dú)的上下文,內(nèi)存占用低
- 切換成本低,性能更高
- 需要編譯器提供語(yǔ)義支持
- 只能在這個(gè)生成器內(nèi)掛起此協(xié)程,無(wú)法在嵌套函數(shù)中掛起此協(xié)程
- 異步代碼必須都有對(duì)應(yīng)的關(guān)鍵字
對(duì)稱(chēng)協(xié)程與非對(duì)稱(chēng)協(xié)程
- 對(duì)稱(chēng)協(xié)程 Symmetric Coroutine:任何一個(gè)協(xié)程都是相互獨(dú)立且平等的,調(diào)度權(quán)可以在任意協(xié)程之間轉(zhuǎn)移。
- 非對(duì)稱(chēng)協(xié)程 Asymmetric Coroutine:協(xié)程出讓調(diào)度權(quán)的目標(biāo)只能是它的調(diào)用者,即協(xié)程之間存在調(diào)用和被調(diào)用關(guān)系。
對(duì)稱(chēng)協(xié)程提供了更高的并發(fā)性和靈活性,適合需要高并發(fā)
處理的場(chǎng)景;而非對(duì)稱(chēng)協(xié)程則在某些控制流固定
的場(chǎng)景下更為適用。