日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

深度解密Go语言之基于信号的抢占式调度

發布時間:2024/4/11 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深度解密Go语言之基于信号的抢占式调度 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

不知道大家在實際工作中有沒有遇到過老版本 Go 調度器的坑:死循環導致程序“死機”。我去年就遇到過,并且搞出了一起 P0 事故,還寫了篇弱智的找 bug 文章。

識別事故的本質,并且用一個非常簡單的示例展示出來,是功力的一種體現。那次事故的原因可以簡化成如下的 demo:

demo-1

我來簡單解釋一下上面這個程序。在主 goroutine 里,先用 GoMAXPROCS 函數拿到 CPU 的邏輯核心數 threads。這意味著 Go 進程會創建 threads 個數的 P。接著,啟動了 threads 個數的 goroutine,每個 goroutine 都在執行一個無限循環,并且這個無限循環只是簡單地執行 x++。

接著,主 goroutine sleep 了 1 秒鐘;最后,打印 x 的值。

你可以自己思考一下,輸出會是什么?

如果你想出了答案,接著再看下面這個 demo:

demo-2

我也來解釋一下,在主 goroutine 里,只啟動了一個 goroutine(雖然程序里用了一個 for 循環,但其實只循環了一次,完全是為了和前面的 demo 看起來更協調一些),同樣執行了一個 x++ 的無限 for 循環。

和前一個 demo 的不同點在于,在主 goroutine 里,我們手動執行了一次 GC;最后,打印 x 的值。

如果你能答對第一題,大概率也能答對第二題。

下面我就來揭曉答案。

其實我留了一個坑,我沒說用哪個版本的 Go 來運行代碼。所以,正確的答案是:

Go 版本demo-1demo-2
1.13卡死卡死
1.1400

這個其實就是 Go 調度器的坑了。

假設在 demo-1 中,共有 4 個 P,于是創建了 4 個 goroutine。當主 goroutine 執行 sleep 的時候,剛剛創建的 4 個 goroutine 馬上就把 4 個 P 霸占了,執行死循環,而且竟然沒有進行函數調用,就只有一個簡單的賦值語句。Go 1.13 對這種情況是無能為力的,沒有任何辦法讓這些 goroutine 停下來,進程對外表現出“死機”。

demo-1 示意圖

由于 Go 1.14 實現了基于信號的搶占式調度,這些執行無限循環的 goroutine 會被調度器“拿下”,P 就會空出來。所以當主 goroutine sleep 時間到了之后,馬上就能獲得 P,并得以打印出 x 的值。至于 x 為什么輸出的是 0,不太好解釋,因為這是一種未定義(有數據競爭,正常情況下要加鎖)的行為,可能的一個原因是 CPU 的 cache 沒有來得及更新,不過不太好驗證。

理解了這個 demo,第二個 demo 其實是類似的道理:

demo-2 示意圖

當主 goroutine 主動觸發 GC 時,需要把所有當前正在運行的 goroutine 停止下來,即 stw(stop the world),但是 goroutine 正在執行無限循環,沒法讓它停下來。當然,Go 1.14 還是可以搶占掉這個 goroutine,從而打印出 x 的值,也是 0。

Go 1.14 之前的版本,能否搶占一個正在執行死循環的 goroutine 其實是有講究的:

能否被搶占,不是看有沒有調用函數,而是看函數的序言部分有沒有插入擴棧檢測指令。

如果沒有調用函數,肯定不會被搶占。

有些雖然也調用了函數,但其實不會插入檢測指令,這個時候也不會被搶占。

像前面的兩個 demo,不可能有機會在函數擴棧檢測期間主動放棄 CPU 使用權,從而完成搶占,因為沒有函數調用。具體的過程后面有機會再寫一篇文章詳細講,本文主要看基于信號的搶占式調度如何實現。

preemptone

一方面,Go 進程在啟動的時候,會開啟一個后臺線程 sysmon,監控執行時間過長的 goroutine,進而發出搶占。另一方面,GC 執行 stw 時,會讓所有的 goroutine 都停止,其實就是搶占。這兩者都會調用 preemptone() 函數。

preemptone() 函數會沿著下面這條路徑:

preemptone->preemptM->signalM->tgkill

向正在運行的 goroutine 所綁定的的那個 M(也可以說是線程)發出 SIGURG 信號。

注冊 sighandler

每個 M 在初始化的時候都會設置信號處理函數:

initsig->setsig->sighandler

信號執行過程

我們從“宏觀”層面看一下信號的執行過程:

信號執行過程

主程序(線程)正在“勤勤懇懇”地執行指令:它已經執行完了指令 m,接著就要執行指令 m+1 了……不幸在這個時候發生了,線程收到了一個信號,對應圖中的 ①。

接著,內核會接管執行流,轉而去執行預先設置好的信號處理器程序,對應到 Go 里,就是執行 sighandler,對應圖中的 ② 和 ③。

最后,執行流又交到線程手上,繼續執行指令 m+1,對應圖中的 ④。

這里其實涉及到了一些現場的保護和恢復,內核都幫我們搞定了,我們不用操心。

dosigPreempt

當線程收到 SIGURG 信號的時候,就會去執行 sighandler 函數,核心是 doSigPreempt 函數。

func?sighandler(sig?uint32,?info?*siginfo,?ctxt?unsafe.Pointer,?gp?*g)?{...if?sig?==?sigPreempt?&&?debug.asyncpreemptoff?==?0?{doSigPreempt(gp,?c)}... }

doSigPreempt 這個函數其實很短,一會兒就執行完了。

func?doSigPreempt(gp?*g,?ctxt?*sigctxt)?{...if?ok,?newpc?:=?isAsyncSafePoint(gp,?ctxt.sigpc(),?ctxt.sigsp(),?ctxt.siglr());?ok?{//?Adjust?the?PC?and?inject?a?call?to?asyncPreempt.ctxt.pushCall(funcPC(asyncPreempt),?newpc)}... }

isAsyncSafePoint 函數會返回當前 goroutine 能否被搶占,以及從哪條指令開始搶占,返回的 newpc 表示安全的搶占地址。

接著,pushCall 調整了一下 SP,設置了幾個寄存器的值就返回了。按理說,返回之后,就會接著執行指令 m+1 了,但那還怎么實現搶占呢?其實魔法都在 pushCall 這個函數里。

pushCall

在分析這個函數之前,我們需要先復習一下 Go 函數的調用規約,重點回顧一下 CALL 和 RET 指令就行了。

call 和 ret 指令

call 指令可以簡單地理解為 push ip + JMP。這個 ip 其實就是返回地址,也就是調用完子函數接下來該執行啥指令的地址。所以 push ip 就是在 call 一個子函數之前,將返回地址壓入棧中,然后 JMP 到子函數的地址執行。

ret 指令和 call 指令剛好相反,它將返回地址從棧上 pop 到 IP 寄存器,使得 CPU 從這個地址繼續執行。

理解了 call 和 ret,我們再來分析 pushCall 函數:

func?(c?*sigctxt)?pushCall(targetPC,?resumePC?uintptr)?{//?Make?it?look?like?we?called?target?at?resumePC.sp?:=?uintptr(c.rsp())sp?-=?sys.PtrSize*(*uintptr)(unsafe.Pointer(sp))?=?resumePCc.set_rsp(uint64(sp))c.set_rip(uint64(targetPC)) }

注意看這行注釋:

//?Make?it?look?like?we?called?target?at?resumePC.

它清晰地說明了這個函數的作用:讓 CPU 誤以為是 resumePC 調用了 targetPC。而這個 resumePC 就是上一步調用 isAsyncSafePoint 函數返回的 newpc,它代表我們搶占 goroutine 的指令地址。

前兩行代碼將 SP 下移了 8 個字節,并且把 resumePC 入棧(注意,它其實是一個返回地址),接著把 targetPC 設置到 ip 寄存器,sp 設置到 SP 寄存器。這使得從內核返回到用戶態執行時,不是從指令 m+1,而是直接從 targetPC 開始執行,等到 targetPC 執行完,才會返回到 resumePC 繼續執行。整個過程就像是 resumePC 調用了 targetPC 一樣。而 targetPC 其實就是 funcPC(asyncPreempt),也就是搶占函數。

于是我們可以看到,信號處理器程序 sighandler 只是將一個異步搶占函數給“安插”進來了,而真正的搶占過程則是在 asyncPreempt 函數中完成。

異步搶占

當執行完 sighandler,執行流再次回到線程。由于 sighandler 插入了一個 asyncPreempt 的函數調用,所以 goroutine 原本的任務就得不到推進,轉而執行 asyncPreempt 去了:

asyncPreempt 調用鏈路

mcall(fn) 的作用是切到 g0 棧去執行函數 fn, fn 永不返回。在 mcall(gopreempt_m) 這里,fn 就是 gopreempt_m。

gopreempt_m 直接調用 goschedImpl:

goschedImpldropg

最精彩的部分就在 goschedImpl 函數。它首先將 goroutine 的狀態從 running 改成 runnable;接著調 dropg 將 g 和 m 解綁;然后調用 globrunqput 將 goroutine 丟到全局可運行隊列,由于是全局可運行隊列,所以需要加鎖。最后,調用 schedule() 函數進入調度循環。關于調度循環,可以看這篇文章。

運行 schedule 函數用的是 g0 棧,它會去尋找其他可運行的 goroutine,包括從當前 P 本地可運行隊列獲取、從全局可運行隊列獲取、從其他 P 偷等方式找到下一個可運行的 goroutine 并執行。

至此,這個線程就轉而去執行其他的 goroutine,當前的 goroutine 也就被搶占了。

那被搶占的這個 goroutine 什么時候會再次得到執行呢?

因為它已經被丟到全局可運行隊列了,所以它的優先級就會降低,得到調度的機會也就降低,但總還是有機會再次執行的,并且它會從調用 mcall 的下一條指令接著執行。

還記得 mcall 函數的作用嗎?它會切到 g0 棧執行 gopreempt_m,自然它也會保存 goroutine 的執行進度,其實就是 SP、BP、PC 寄存器的值,當 goroutine 再次被調度執行時,就會從原來的執行流斷點處繼續執行下去。

總結

本文講述了 Go 語言基于信號的異步搶占的全過程,一起來回顧下:

  • M 注冊一個 SIGURG 信號的處理函數:sighandler。

  • sysmon 線程檢測到執行時間過長的 goroutine、GC stw 時,會向相應的 M(或者說線程,每個線程對應一個 M)發送 SIGURG 信號。

  • 收到信號后,內核執行 sighandler 函數,通過 pushCall 插入 asyncPreempt 函數調用。

  • 回到當前 goroutine 執行 asyncPreempt 函數,通過 mcall 切到 g0 棧執行 gopreempt_m。

  • 將當前 goroutine 插入到全局可運行隊列,M 則繼續尋找其他 goroutine 來運行。

  • 被搶占的 goroutine 再次調度過來執行時,會繼續原來的執行流。


  • 期待你的關注~

    總結

    以上是生活随笔為你收集整理的深度解密Go语言之基于信号的抢占式调度的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 日本性视频网站 | 男生桶女生肌肌 | 九草视频在线 | 91日本视频| 国产精品综合久久久久久 | a级无毛片 | 亚洲天堂成人在线观看 | 噼里啪啦免费观看 | 男男做爰猛烈叫床爽爽小说 | 熟女丝袜一区 | 九色视频网 | av全黄| 天堂在线一区二区 | aaa毛片视频 | 亚洲一区二区三区人妻 | 欧美啪啪小视频 | 一级黄色片免费在线观看 | 污导航在线 | 91九色高潮 | 亚洲小视频在线播放 | 精品一区二区免费看 | 一个人在线观看免费视频www | 天天干天天爽 | 少妇高潮露脸国语对白 | 乱淫的女高中暑假调教h | 日韩精品一区三区 | 激情影音 | 色老头在线一区二区三区 | 小辣椒福利视频导航 | 中国在线观看视频高清免费 | 国产精品羞羞答答在线观看 | 欧美sm凌虐视频网站 | 九九热视频免费观看 | 五月情网 | 婷婷婷色 | 伊人网站在线观看 | 国产综合视频在线 | 亚洲成人中文字幕在线 | 91嫩草视频在线观看 | 精品久久网 | 国产精品不卡在线 | 国产天堂 | 一色桃子juy758在线播放 | 国产成人无码www免费视频播放 | 亚洲综合p | 四虎啪啪 | av在线综合网 | 亚洲情欲网 | 无套在线观看 | 亚洲喷潮| a级免费网站 | 日韩一二三四区 | 欧美性生活网站 | 夏目彩春娇喘呻吟高潮迭起 | 色综合a| 国产制服在线 | 五月天校园春色 | 欧美a v在线 | 亚洲av人无码激艳猛片服务器 | 成人免费毛片高清视频 | hs网站在线观看 | 亚洲免费黄网 | 国产一区 在线播放 | av在线日韩 | 无码人妻丰满熟妇精品区 | 午夜在线观看影院 | 精一区二区 | 日韩综合区 | 亚洲一区二区三区高清 | 精品欧美一区二区精品少妇 | 久久久人妻无码一区二区 | 久久久久国产一区 | 国产精品一品二区三区的使用体验 | 黄色成人免费观看 | 久久精品超碰 | 亚洲 欧美 另类 综合 偷拍 | 久久偷看各类wc女厕嘘嘘偷窃 | 精品国产一区二区三区久久久蜜月 | 免费成人深夜夜视频 | 欧美1区2区3区 | 夜福利视频 | 国产剧情一区 | 欧美六区| 大尺度做爰无遮挡露器官 | 麻豆成人久久精品一区二区三区 | 日本一区二区在线观看视频 | 少妇诱惑av | 东京热一区二区三区四区 | 国产精品一区二区三区在线免费观看 | 中文字幕av第一页 | 欧美a级黄色片 | 亚洲第一区在线 | 99久久婷婷国产一区二区三区 | 超碰国产人人 | 国产精品一区二区av | 国产在线xx| 中文字幕在线观看免费 | 精品少妇人妻一区二区黑料社区 | 国产日本在线 |