W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
goroutine本來是設(shè)計為協(xié)程形式,但是隨著調(diào)度器的實現(xiàn)越來越成熟,Go在1.2版中開始引入比較初級的搶占式調(diào)度。
Go在設(shè)計之初并沒考慮將goroutine設(shè)計成搶占式的。用戶負責讓各個goroutine交互合作完成任務(wù)。一個goroutine只有在涉及到加鎖,讀寫通道或者主動讓出CPU等操作時才會觸發(fā)切換。
垃圾回收器是需要stop the world的。如果垃圾回收器想要運行了,那么它必須先通知其它的goroutine合作停下來,這會造成較長時間的等待時間。考慮一種很極端的情況,所有的goroutine都停下來了,只有其中一個沒有停,那么垃圾回收就會一直等待著沒有停的那一個。
搶占式調(diào)度可以解決這種問題,在搶占式情況下,如果一個goroutine運行時間過長,它就會被剝奪運行權(quán)。
引入搶占式調(diào)度,會對最初的設(shè)計產(chǎn)生比較大的影響,Go還只是引入了一些很初級的搶占,并沒有像操作系統(tǒng)調(diào)度那么復雜,沒有對goroutine分時間片,設(shè)置優(yōu)先級等。
只有長時間阻塞于系統(tǒng)調(diào)用,或者運行了較長時間才會被搶占。runtime會在后臺有一個檢測線程,它會檢測這些情況,并通知goroutine執(zhí)行調(diào)度。
目前并沒有直接在后臺的檢測線程中做處理調(diào)度器相關(guān)邏輯,只是相當于給goroutine加了一個“標記”,然后在它進入函數(shù)時才會觸發(fā)調(diào)度。這么做應(yīng)該是出于對現(xiàn)有代碼的修改最小的考慮。
前面講Go程序的初始化過程中有提到過,runtime開了一條后臺線程,運行一個sysmon函數(shù)。這個函數(shù)會周期性地做epoll操作,同時它還會檢測每個P是否運行了較長時間。
如果檢測到某個P狀態(tài)處于Psyscall超過了一個sysmon的時間周期(20us),并且還有其它可運行的任務(wù),則切換P。
如果檢測到某個P的狀態(tài)為Prunning,并且它已經(jīng)運行了超過10ms,則會將P的當前的G的stackguard設(shè)置為StackPreempt。這個操作其實是相當于加上一個標記,通知這個G在合適時機進行調(diào)度。
目前這里只是盡最大努力送達,但并不保證收到消息的goroutine一定會執(zhí)行調(diào)度讓出運行權(quán)。
前面說的,將stackguard設(shè)置為StackPreempt實際上是一個比較trick的代碼。我們知道Go會在每個函數(shù)入口處比較當前的棧寄存器值和stackguard值來決定是否觸發(fā)morestack函數(shù)。
將stackguard設(shè)置為StackPreempt作用是進入函數(shù)時必定觸發(fā)morestack,然后在morestack中再引發(fā)調(diào)度。
看一下StackPreempt的定義,它是大于任何實際的棧寄存器的值的:
// 0xfffffade in hex.
#define StackPreempt ((uint64)-1314)
然后在morestack中加了一小段代碼,如果發(fā)現(xiàn)stackguard為StackPreempt,則相當于調(diào)用runtime.Gosched。
所以,到目前為止Go的搶占式調(diào)度還是很初級的,比如一個goroutine運行了很久,但是它并沒有調(diào)用另一個函數(shù),則它不會被搶占。當然,一個運行很久卻不調(diào)用函數(shù)的代碼并不是多數(shù)情況。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: