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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

从Golang调度器的作者视角探究其设计之道!

發(fā)布時間:2024/4/11 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 从Golang调度器的作者视角探究其设计之道! 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

導語?|?Golang核心開發(fā)人員、goroutine調(diào)度的設計者Dmitry Vyukov,在2019年的一個talk里深入淺出地闡述了goroutine調(diào)度的設計思想以及一些優(yōu)化的細節(jié)。本文是筆者結(jié)合自身經(jīng)驗和認知的一點觀后感,采用從零開始層層遞進的方法,總結(jié)剖析了其背后的軟件設計思想,希望對讀者更好地理解goroutine調(diào)度GMP模型會有所幫助。

前言

視頻地址:

https://2019.hydraconf.com/2019/talks/7336ginp0kke7n4yxxjvld/

這個視頻我以前看過,近幾天刷到便又看了一遍,真是有聽君一席話受益匪淺之感。毫不夸張地說,本視頻在筆者看過的所有資料中,對于GMP為什么要有Processor這點,講得最為清楚。視頻中對goroutine調(diào)度模型的講解,真可謂深入淺出!下面筆者將自己的一些觀感整理分享給大家,還沒看過視頻的同學,建議先看完本文再去看,收獲會更大。

為了表達方便,本文會沿用golang里面的GMP縮寫:

  • G ——?goroutine

  • M —— 機器線程

  • P —— 對處理器的抽象

一、設計并發(fā)編程模型

goroutine調(diào)度的設計目標,其實就是設計一種高效的并發(fā)編程模型:

  • 從開發(fā)的角度只需要一個關鍵詞(go)就能創(chuàng)建一個執(zhí)行會話,很方便使用,即開發(fā)效率是高效的。

  • 從運行態(tài)的角度,上述創(chuàng)建的會話也能高效的被調(diào)度執(zhí)行,即運行效率也是高效的。

我們可以近似將goroutine看待為協(xié)程(一些代碼邏輯+一個棧上下文),如果讀者用C/C++造過協(xié)程框架的輪子,會很容易理解這點。

:除了高效之外,還有其他幾個目標,如無大小限制的goroutine棧,公平的調(diào)度策略等。

二、從零開始:從多線程說起

想要實現(xiàn)并發(fā)的執(zhí)行流,最直截了當?shù)?#xff0c;自然就是多線程。由此便得出初始思路:每個goroutine對應一個線程

從并發(fā)的功能角度來講,該方案固然可以實現(xiàn)并發(fā),但性能方面卻很不堪,尤其是在并發(fā)很重的時候,成千上萬個線程的資源占用、創(chuàng)建銷毀、調(diào)度帶來的開銷會很巨大。

三、更進一步:線程池的方案

既然線程太多不好,那我們可以很輕易地做出一點改善,控制一下線程數(shù)量,如此便得到更進一步的方案:線程池,限定只啟動N個線程。

由于該方案下,可能是M個goroutine,N個線程,因而顯然需要考慮一個問題:對于一個goroutine,它到底該由哪個線程去執(zhí)行?我們可以簡單地采用一個全局的Global Run Queue,然后讓所有線程主動去獲取goroutine來執(zhí)行,示意如下:

這樣做在線程少的時候,如果調(diào)度行為不是很頻繁,可能問題不大。但當線程較多時,就會有scalable的問題,mutex的互斥競爭會非常激烈(考慮到基于時間片的搶占行為,實際上調(diào)度必然是很頻繁的)。

四、初具雛形:線程分治

在多線程編程領域中,互斥處理可以稱得上是“名聲在外”,需極其小心地去應對。最常見的解決方案,并不是如何精妙地去lock free,而是直接通過 “數(shù)據(jù)分治”和“邏輯分治”來避免做復雜的加鎖互斥,將各個線程按橫向(載荷分組)或縱向(邏輯劃分)進行切分來處理工作。

通過數(shù)據(jù)分治的思想,我們就可以得到改進的方案:每個線程分別處理一批G,進行線程分治。將所有G分開放到各線程自己的存儲中,即所謂的Local Run Queue中。示意如下:

:Global Run Queue也還繼續(xù)存在的,有關它存在的細節(jié)非本文重點,這里不做展開。

至此,調(diào)度模型已具雛形。

讓我們繼續(xù)分析確認一下,該模型是否真的解決了scalable的問題。上述模型下,為了充分利用CPU,每個線程要按一定的策略去Steal其他線程Local Run Queue里面的G來執(zhí)行,以免線程之間存在load balance問題(有些太閑,有些又太忙)

因此在線程很多的時候,存在大量的無意義加鎖Steal操作,因為其他線程的Local Run Queue可能也常常都是空的。還有另一個問題,由于現(xiàn)在的一些內(nèi)存資源是綁定在線程上面的,會導致線程數(shù)量和資源占用規(guī)模緊耦合。當線程數(shù)量多的時候,資源消耗也會比較大。

注:在N核的機器環(huán)境下,假如我們設定線程池大小為N,由于系統(tǒng)調(diào)用的存在(關于系統(tǒng)調(diào)用的處理見后文),實際的線程數(shù)量會超過N。

五、趨于完善:將資源和線程解耦

既然每個線程一份資源也不合適,那么我們可以仿照線程池的思路,單獨做一個資源池,做計算存儲分離:把Local Run Queue及相關存儲資源都挪出去,并依然限定全局一共N份,即可實現(xiàn)資源規(guī)模與系統(tǒng)中的真實線程數(shù)量的解耦。線程每次從對應的數(shù)據(jù)結(jié)構(gòu)(Processor)中獲取goroutine去執(zhí)行,Local Run Queue及其他一些相關存儲資源都掛在Processor下。這樣加一層Processor的抽象之后,便得到眾所周知的GMP模型:

現(xiàn)在的調(diào)度模型已趨于完善,不過前面我們主要側(cè)重講的是如何高效,還未討論到調(diào)度的另一個關鍵問題:公平性與搶占,接下來我們看看如何實現(xiàn)搶占。

六、還要公平:調(diào)度搶占

參考操作系統(tǒng)CPU的調(diào)度策略,通常各進程會分時間片,時間片用完了就輪到其他進程。在golang里也可以如此,不能讓一些goroutine長期霸占著運行資源不退出,必須實現(xiàn)基于時間片的“搶占”。

那怎么搶占呢,需要監(jiān)測goroutine執(zhí)行時間片是否用完了。如果要檢查系統(tǒng)中的各種狀態(tài)變化、事件發(fā)生情況,通常會有中斷與輪詢兩種思路,中斷是由一個中控方來做檢查與控制,而輪詢則是各個參與方按一定的策略主動check詢問。因此對于goroutine搶占而言,有以下兩種解決方案:

  • Signals,通過信號來中斷原來的線程執(zhí)行。

  • Cooperative checks,通過線程間歇性輪詢自己check運行的時間片情況來主動暫停。

二者的優(yōu)劣對比如下:

因為golang其實是有runtime的,而且代碼編譯生成也都是golang編譯器控制的,綜合優(yōu)劣分析,選擇后者會比較合理。

對于Cooperative checks的方案,從代碼編譯生成的角度看,很容易做check指令的埋點。且因為golang本來就要做動態(tài)增長棧,在函數(shù)入口處會插入檢查是否該擴棧的指令,正好利用這一點來做相關的檢查實現(xiàn)(這里有一些優(yōu)化細節(jié),可以使得基于時間片的搶占開銷也較小)

插入check指令的做法,會導致該方案存在一個理論缺陷:若有一個死循環(huán),里面的所有代碼都不包含check指令,那依然會無法搶占,不過現(xiàn)實中基本不存在這種情況,總會做函數(shù)調(diào)用、訪問channel等類似操作,因此不足為慮。

除此以外還有一個系統(tǒng)調(diào)用的問題,當線程一旦進入系統(tǒng)調(diào)用后,也會脫離runtime的控制。試想萬一系統(tǒng)調(diào)用阻塞了呢,基于Cooperative checks的方案,此時又無法進行搶占,是不是整個線程也就罷工了。所以為了維持整個調(diào)度體系的高效運轉(zhuǎn),必然要在進入系統(tǒng)調(diào)用之前要做點什么以防患未然。Dmitry這里采用的辦法也很直接,對于即將進入系統(tǒng)調(diào)用的線程,不做搶占,而是由它主動讓出執(zhí)行權(quán)。線程A在系統(tǒng)調(diào)用之前handoff讓出Processor的執(zhí)行權(quán),喚醒一個idle線程B來做交接。當線程A從系統(tǒng)調(diào)用返回時,不會繼續(xù)執(zhí)行,而是將G放到run queue,然后進入idle狀態(tài)等待喚醒,這樣一來便能確保活躍線程數(shù)依然與Processor數(shù)量相同。

七、設計思想的小結(jié)

這里recap一下,把前文涉及到的一些軟件設計思想羅列如下:

  • 線程池,通過多線程提供更大的并發(fā)處理能力,同時又避免線程過多帶來的過大開銷。

  • 資源池,對有一定規(guī)模約束的資源進行池化管理,如內(nèi)存池、機器池、協(xié)程池等,前面的線程池也可以算作此類。

  • 計算存儲分離,分別從邏輯、數(shù)據(jù)結(jié)構(gòu)兩個角度進行設計,規(guī)劃二者的耦合關系。

加一層,這個是萬能大法,不贅述。

  • 中斷與輪詢,用于監(jiān)測系統(tǒng)中的各種狀態(tài)變化、事件變化,通常來講中斷會比輪詢更高效。

八、其他內(nèi)容

本文的重點在GMP模型,因此還有一些其他的內(nèi)容,文中并未詳細展開:

  • Local Run Queue里面的G所創(chuàng)建的G會放到同樣的Local Run Queue(如果滿了還是會放GRQ),而且會限制被偷走,這樣可以加強Locality,同時為了保證公平也做了時間片繼承,以免不停創(chuàng)建G會長期霸占運行資源。

  • 被搶占的G會放到全局的G隊列(Global Run Queue),GRQ會每61次tick檢查一次,Dmitry針對這個61解釋了一番,但筆者認為還是有點拍腦袋的感覺。

  • G的棧采用的是Growable stack方案,在函數(shù)入口會有棧檢查的指令,如需擴容棧,會拷貝到新申請的更大的棧。

  • Go runtime還會用Background thread來運行一些相對特別的G(如 Network Poller、Timer)。

以上這些內(nèi)容,大家可以去視頻學習。

:本文基于2019的talk,不知最新版本的調(diào)度機制是否有進一步的調(diào)整,不過無論調(diào)整與否,這并不妨礙我們對GMP設計思想的學習。

九、進一步的改進

有同學在與筆者討論時提了一個問題:還可以怎么繼續(xù)優(yōu)化,這真的是一個非常好的問題,這里將該問題的回答也放入文章。

不單純針對GMP,話題稍微放大一點,下面簡單聊聊goroutine調(diào)度機制的一些優(yōu)化可能。

Dmitry自己在視頻最后說的future work方向:

  • 在很多cpu core的情況下,活躍線程數(shù)比較多,work steal的開銷依舊有些浪費。

  • 死循環(huán)不含cooperative check指令的這種edge情況的還沒解決。

  • 對于網(wǎng)絡和timer的goroutine處理是使用全局方式的,不好scale。

以下純屬個人探討:

  • 首先整體上現(xiàn)在的模型已經(jīng)比較完善,如何進一步優(yōu)化要看實踐場景遇到的問題,以及profile數(shù)據(jù)情況,只有問題和數(shù)據(jù)明確了,才清楚進一步工作的宏觀重點(工作中也是,做性能優(yōu)化需要有宏觀視角)。

  • 因為goroutine調(diào)度是屬于協(xié)程類的調(diào)度,這里或許可以借鑒原來各種協(xié)程框架的思路做一些對比考慮。

  • 由于筆者并沒細看過代碼,不大清楚work steal的overhead構(gòu)成,或許可以設計其他的rebalance方式,例如換個視角,不是去steal,而是由runtime統(tǒng)一rebalance再收集派發(fā)。

目前就先想到這些,歡迎討論。

十、歡樂游戲的協(xié)程框架

基于上面那個問題的回答,這里也補充介紹一下歡樂游戲協(xié)程框架(基于C++)中采用的處理機制,因為是純業(yè)務自用,所以從設計要求上就低很多,不少點直接都可以不去考慮(這也說明了,有些時候再好的既有流行方案,從性能上講可能也比不過自家的破輪子,當然自家的輪子泛化不足,肯定普適性就會差很多)

  • 協(xié)程調(diào)度采用最簡單的單線程模型

  • 設計之初就沒考慮用多線程充分利用多核資源,我們認為直接多部署一些進程就好。

  • 對于一定要把單進程承載做的很高的極少數(shù)場景,可以專事專辦,做專門的方案即可。

  • 協(xié)程采用固定的棧大小

  • 通常幾百k就夠了(例如256k或者512k),創(chuàng)建協(xié)程的時候就預分配好。

  • 這點確實不如growable stack那么高明,但是從實踐看也算夠了,這樣就免去了stack動態(tài)增長的工作(從應用編程的視角看,其實C++里我們可能因為無法做指令插入埋點,本來就做不到stack動態(tài)增長)。

  • 我們在相鄰stack之間加一些寫保護page,這樣一旦踩了就會 coredump。

  • 同時通過編譯選項,限制單層棧大小不能超過某個閾值。

  • 協(xié)程調(diào)度完全不考慮公平性,全部采用主動handoff策略

    對于某個協(xié)程,如果它要持續(xù)運行,就任它運行,直到要進行阻塞類操作(典型如RPC調(diào)用),才會交出執(zhí)行權(quán)。實際上對于業(yè)務來講,微觀層面幾十毫秒內(nèi)哪個協(xié)程多占了一點執(zhí)行權(quán)真的無所謂,不用太講究公平性。假如真的有些協(xié)程餓死了,那說明業(yè)務都已經(jīng)過載了(就是時時刻刻都在跑其他協(xié)程,cpu100),此時討論公平也沒什么意義了。假如我們真的要做,因為做不到指令插入,只能采用Signals信號中斷的方式,在注冊的信號處理函數(shù)中直接按需切棧。

  • 主協(xié)程主控循環(huán)tick直接管理協(xié)程,協(xié)程調(diào)度不涉及background thread

  • 網(wǎng)絡IO、第三方異步API tick驅(qū)動、timer管理、協(xié)程創(chuàng)建銷毀管理等都是主協(xié)程在做。

  • 主控循環(huán)中,如果要創(chuàng)建或恢復協(xié)程,就任由它去立即執(zhí)行,一直跑到它阻塞掛起再返回主協(xié)程。

協(xié)程切換示意圖,圖注:1、2、5在主協(xié)程,3、4在業(yè)務協(xié)程,主協(xié)程和業(yè)務協(xié)程都在主線程內(nèi)。

  • 仍可以有基于邏輯分治的多線程

  • 框架不是真的只有一個線程,按功能拆分的日志線程,依然可以存在。

  • 對于一些第三方異步API,如果其tick本身實現(xiàn)不好,導致大量占據(jù)了運行時間,也可以分拆線程,然后用隊列之類的機制和主線程的主協(xié)程交互即可。

  • 對于網(wǎng)絡IO也同上。

總之,這種基于邏輯分治做線程拆分的改造都是很簡單的,也并不會影響到核心協(xié)程調(diào)度的機制。


如果有什么疑問,想深入學習,歡迎大家加入極客星球,讓我們一起進步,掌握核心技術(shù),既能掙錢又能抗壓,掙錢和事業(yè)兩不誤,對星球感興趣的,點擊查看->?極客星球,公眾號回復“優(yōu)惠卷”,或者掃描下面二維碼可以加入。里面還有之前幾期的直播分享視頻,星球分享的東西都很干貨。

同時我每周都會提問幾道非常經(jīng)典的面試題,通過參與這些經(jīng)典的面試題分析驗證,我們可以徹底理解大廠面試的核心知識點,需要深入交流學習同學,可以加入極客星球,和大家一起快速成長:

  • 大廠求職核心原理1v1指導(職位,簡歷,面試,策略等一條龍優(yōu)化)

  • 技術(shù)問題幫忙分析解答(有專屬VIP群)

可以加我微信詳細了解。

  • 大廠技術(shù)路線

  • 后臺開發(fā)進階

  • 開源項目學習

  • 直播交流分享(已經(jīng)分享了8期,加入星球可以看回放)

  • 技術(shù)視野

  • 按需提供經(jīng)典資料,節(jié)約你時間

  • 實戰(zhàn)技能分享

- END -


看完一鍵三連在看轉(zhuǎn)發(fā)點贊

是對文章最大的贊賞,極客重生感謝你

推薦閱讀

定個目標|建立自己的技術(shù)知識體系


大廠后臺開發(fā)基本功修煉路線和經(jīng)典資料

個人學習方法分享


你好,這里是極客重生,我是阿榮,大家都叫我榮哥,從華為->外企->到互聯(lián)網(wǎng)大廠,目前是大廠資深工程師,多次獲得五星員工,多年職場經(jīng)驗,技術(shù)扎實,專業(yè)后端開發(fā)和后臺架構(gòu)設計,熱愛底層技術(shù),豐富的實戰(zhàn)經(jīng)驗,分享技術(shù)的本質(zhì)原理,希望幫助更多人蛻變重生,拿BAT大廠offer,培養(yǎng)高級工程師能力,成為技術(shù)專家,實現(xiàn)高薪夢想,期待你的關注!點擊藍字查看我的成長之路

校招/社招/簡歷/面試技巧/大廠技術(shù)棧分析/后端開發(fā)進階/優(yōu)秀開源項目/直播分享/技術(shù)視野/實戰(zhàn)高手等,?極客星球希望成為最有技術(shù)價值星球,盡最大努力為星球的同學提供面試,跳槽,技術(shù)成長幫助!詳情查看->極客星球

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 求點贊,在看,分享三連

總結(jié)

以上是生活随笔為你收集整理的从Golang调度器的作者视角探究其设计之道!的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。