go協(xié)程學(xué)習(xí)筆記

協(xié)程本質(zhì):

go協(xié)程本質(zhì)上還是用線程來運行代碼,只是在多線程上增加了調(diào)度器,通過調(diào)度器讓每一個線程可以執(zhí)行多個協(xié)程。

實現(xiàn)原理:

go協(xié)程使用GPM調(diào)度模型實現(xiàn),具體內(nèi)容如下:

G goroutine協(xié)程

P process 調(diào)度器,為每一個m分配g,

M machine 對應(yīng)操作系統(tǒng)的線程,g的真正執(zhí)行者。

P的數(shù)量默認是CPU核數(shù),也可以通過GOMAXPROCS來指定數(shù)量,每個P都會維護一個runq隊列,用于保存G,P會從隊列頭獲取G交給M執(zhí)行,執(zhí)行完后放入到隊列尾(如果需要繼續(xù)執(zhí)行),通過GPM模型實現(xiàn)了多個協(xié)程并行(不是并發(fā))執(zhí)行,可以最大限度地利用到CPU。

協(xié)程優(yōu)點:

協(xié)程相對于多線程有哪些優(yōu)點呢?

1.上下文切換更輕量

觸發(fā)go上下文切換有兩種場景,1是協(xié)作式搶占引起的協(xié)程切換,2是鎖阻塞\IO阻塞\channel阻塞,兩種場景保存的上下文數(shù)據(jù)結(jié)構(gòu)都是g.sched,它的數(shù)據(jù)結(jié)構(gòu)如下:

與棧相關(guān)的SP和BP寄存器

PC

用于保存函數(shù)閉包的上下文,也就是DX寄存器

相比多線程協(xié)程切換只需要修改少數(shù)寄存器內(nèi)容,所以更輕量,但一個線程使用到的寄存器總大小非常有限,就算全部內(nèi)容更新也不會消耗多少資源, 但為什么大部分人都會說協(xié)程減少了上下文切換帶來的開銷? 這里就要看上下文的定義了,如果不僅包括協(xié)程執(zhí)行時所需要的數(shù)據(jù),還包括內(nèi)核態(tài)與用戶態(tài)的切換則這句是對的。

2.節(jié)省了內(nèi)核態(tài)與用戶態(tài)的切換

用戶態(tài)切換與內(nèi)核態(tài)之間切換一次可能花費1000cycles,線程切換由操作系統(tǒng)內(nèi)核完成,所以需要從用戶態(tài)切換到內(nèi)核態(tài)然后再切回用戶態(tài),而協(xié)程則不需要進行狀態(tài)的切換。

下面這段話測試結(jié)果描述了上下文切換帶來的損耗:

Since all the context switching is happening at the application level, we don’t lose the same ~12k instructions (on average) per context switch that we were losing when using Threads. In Go, those same context switches are costing you ~200 nanoseconds or ~2.4k instructions.

摘自:https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part2.html

3.提高了IO類任務(wù)的效率?

CPU執(zhí)行線程是搶占式的,GO協(xié)程也是協(xié)作搶占式,工作原理都一樣,都是為每次執(zhí)行分配固定的時間片,如果只從這一點看,它們執(zhí)行io類任務(wù)效率不會有多大差別,但實際上產(chǎn)生效果還是有區(qū)別的,這個后面會提到。

協(xié)程引發(fā)的思考

1.操作系統(tǒng)需要支持協(xié)程嗎?

協(xié)程能減少線程切換帶來的開銷,那操作系統(tǒng)有必要開發(fā)一種支持協(xié)程的線程嗎,或者用其它方式支持協(xié)程的API?? 如果這樣做確實可以降低用戶或各語言的開發(fā)難度,那這種方案可行嗎?或者有必要嗎?

個人覺得雖然可行但沒必要,這樣會提高系統(tǒng)的復(fù)雜性,尤其是協(xié)程隊列的維護,這樣的功能更貼近業(yè)務(wù)應(yīng)該讓各開發(fā)語言來實現(xiàn),而不是底層的操作來實現(xiàn),那這里又引發(fā)了另外一個思考:java需要支持協(xié)程嗎?

2. java需要支持協(xié)程嗎?

暫時不會支持,以后可能會有,理由是JAVA的線程池同樣也有協(xié)程帶來的開銷減少好處,雖然寫協(xié)程代碼更簡潔方便,但JDK1.8開始函數(shù)式編程的完善讓我們創(chuàng)建線程更快捷了。不過如果JAVA支持協(xié)程確實會帶來一些執(zhí)行效果上的改變,例如前面提到的IO類任務(wù),在線程池方案中,只有當(dāng)前活躍的線程會拿到CPU時間片,如果這些線程是IO類會導(dǎo)致正在排隊的其它任務(wù)長時間不能執(zhí)行,這樣大大降低了CPU利用率,雖然提高線程池活躍數(shù)能解決一部分問題,但還是做不到像協(xié)程那樣每個任務(wù)都能公平分配到時間片,所以JAVA實現(xiàn)協(xié)程還是有一點點價值的,同樣運行在java虛擬機上的Kotlin就支持協(xié)程。

求助:

因本人的知識儲備與時間有限,有些問題還需要求助網(wǎng)友:

進程內(nèi)線程切換會觸發(fā)內(nèi)核態(tài)與用戶態(tài)的切換嗎?

在網(wǎng)上查資料用戶態(tài)切到內(nèi)核態(tài)有以下三種情況:

1.當(dāng)程序使用到系統(tǒng)內(nèi)核的數(shù)據(jù)或者程序時就需要進入內(nèi)核態(tài)

2.硬件出現(xiàn)異常,會優(yōu)先執(zhí)行系統(tǒng)程序

3.程序出現(xiàn)系統(tǒng)級錯誤

進程內(nèi)線程切換一般是時間片用完,這種情況并不屬于以上三種,那它會觸發(fā)內(nèi)核態(tài)與用戶態(tài)的切換嗎

問題知乎地址:https://www.zhihu.com/question/451104554

大家?guī)兔υ谥跎匣卮疬@個問題,也可以釘釘我一起討論:jeff07,當(dāng)然上面筆記有描述錯誤也歡迎指正。

名詞解釋:

協(xié)作式搶占:sysmon 協(xié)程標(biāo)記某個協(xié)程運行過久, 需要切換出去, 該協(xié)程在運行函數(shù)時會檢查棧標(biāo)記, 然后主動調(diào)用GoSched()讓出CPU。

SP:stack pointer register 棧指針寄存器(指向棧頂)

BP: base pointer register? 基指針寄存器

DX: 通用寄存器中的數(shù)據(jù)寄存器

參考:

https://mcll.top/2020/04/14/go%E7%9A%84%E5%8D%8F%E7%A8%8B%E4%B8%8A%E4%B8%8B%E6%96%87%E5%88%87%E6%8D%A2/

https://www.zhihu.com/question/20862617

參考重要摘要:


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容