日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:52000
  • 待審:37
  • 小程序:12
  • 文章:1037587
  • 會(huì)員:756

1. 前言

Go 語(yǔ)言最大的魅力就是只需要 go 關(guān)鍵字即可快速創(chuàng)建一個(gè) goroutine ,無(wú)需關(guān)注操作系統(tǒng)的調(diào)度細(xì)節(jié),即可利用上多核輕松開(kāi)發(fā)出高并發(fā)的服務(wù)器應(yīng)用。理解了 Golang 調(diào)度器的機(jī)制,能讓我們寫(xiě)出高性能的并發(fā)程序,也可以提升我們的問(wèn)題定位、性能優(yōu)化的能力。

2. 進(jìn)程、線程、協(xié)程

任何并發(fā)程序,無(wú)論在應(yīng)用層是何形式,最終都要被操作系統(tǒng)所管理。由此涉及到以下幾個(gè)概念:

  • 進(jìn)程 (Process):操作系統(tǒng)分配資源的基本單位
  • 線程 (Thread):CPU 調(diào)度的基本單位,一個(gè)核上同時(shí)只能運(yùn)行一個(gè)線程,單線程的棧內(nèi)存大小約 1MB
  • 協(xié)程 (Coroutine):輕量級(jí)、用戶態(tài)線程。在 Go 語(yǔ)言中,有 goroutine的概念。當(dāng)一個(gè) goroutine 被創(chuàng)建出來(lái)時(shí),分配的棧大小是 2KB,可見(jiàn)其「輕量」

3. 早期的調(diào)度模型:GM 模型

系統(tǒng)的設(shè)計(jì)往往都不是一蹴而就的,伴隨著演進(jìn)和優(yōu)化,Golang scheduler 也不例外。1.1 版本前的 go,使用的是相對(duì)簡(jiǎn)單的 GM 模型來(lái)處理 goroutine 的調(diào)度,GM 實(shí)際是兩個(gè)結(jié)構(gòu)體,即:

  • G(Goroutine): 協(xié)程結(jié)構(gòu)體,使用 go func() 時(shí),就會(huì)創(chuàng)建一個(gè) G
  • M(machine): 處理線程操作的結(jié)構(gòu)體,與操作系統(tǒng)直接交互

GM 模型包含一個(gè)全局協(xié)程隊(duì)列,即所有新建的 G 對(duì)象都會(huì)排隊(duì)等待 M 的處理。如圖:

這樣的設(shè)計(jì)在高并發(fā)下會(huì)帶來(lái)以下問(wèn)題:

What’s wrong with current implementation:

  1. Single global mutex (Sched.Lock) and centralized state. The mutex protects all goroutine-related operations (creation, completion, rescheduling, etc).
  2. Goroutine (G) hand-off (G.nextg). Worker threads (M’s) frequently hand-off runnable goroutines between each other, this may lead to increased latencies and additional overheads. Every M must be able to execute any runnable G, in particular the M that just created the G.
  3. Per-M memory cache (M.mcache). Memory cache and other caches (stack alloc) are associated with all M’s, while they need to be associated only with M’s running Go code (an M blocked inside of syscall does not need mcache). A ratio between M’s running Go code and all M’s can be as high as 1:100. This leads to excessive resource consumption (each MCache can suck up up to 2M) and poor data locality.
  4. Aggressive thread blocking/unblocking. In presence of syscalls worker threads are frequently blocked and unblocked. This adds a lot of overhead.

(原文見(jiàn) Scalable Go Scheduler Design Doc)

翻譯總結(jié)一下,即存在以下優(yōu)化點(diǎn):

  1. 全局鎖、中心化狀態(tài)帶來(lái)的鎖競(jìng)爭(zhēng)導(dǎo)致的性能下降
  2. M 會(huì)頻繁交接 G,導(dǎo)致額外開(kāi)銷(xiāo)、性能下降。每個(gè) M 都得能執(zhí)行任意的 runnable 狀態(tài)的 G
  3. 對(duì)每個(gè) M 的不合理的內(nèi)存緩存分配,進(jìn)行系統(tǒng)調(diào)用被阻塞住的 M 其實(shí)不需要分配內(nèi)存緩存
  4. 強(qiáng)線程阻塞/解阻塞。頻繁的系統(tǒng)調(diào)用導(dǎo)致的線程阻塞/解阻塞帶來(lái)了大量的系統(tǒng)開(kāi)銷(xiāo)。

由此演進(jìn)出經(jīng)典的 GMP 調(diào)度模型

4. 高效的 GMP 調(diào)度模型

為了解決 G 和 M 調(diào)度低效的問(wèn)題,中間層 P(Processor) 被引入了。

  • P(Processor) 即管理 goroutine 資源的處理器

由此得以將原先的系統(tǒng)線程資源管理 M 與 goroutine 對(duì)象 G 解耦。

除此之外,GMP 還引入了幾個(gè)新概念:

  • LRQ (Local Runnable Queue) 本地可運(yùn)行隊(duì)列,這個(gè)「本地」,是針對(duì) P 而言的,是指掛載在一個(gè) P 上的可運(yùn)行 G 隊(duì)列
  • GRQ (Global Runnable Queue) 全局可運(yùn)行隊(duì)列

如下圖所示:


LRQ 與 GRQ

從 runtime 源碼可知,整體的調(diào)度流程如下:

runtime.schedule() {
    // only 1/61 of the time, check the global runnable queue for a G.
    // if not found, check the local queue.
    // if not found,
    //     try to steal from other Ps.
    //     if not, check the global runnable queue.
    //     if not found, poll .NETwork.
}

翻譯一下,即

  • 只有 1/61 的情況下,會(huì)檢查 GRQ 來(lái)獲取一個(gè) G 來(lái)運(yùn)行
  • 如果沒(méi)找到,則檢查 LRQ
  • 如果 LRQ 也為空,則嘗試從其它 P 上偷 G
  • 如果偷不到,就再檢查 GRQ
  • 如果還是沒(méi)事干,就會(huì)從 Network Poller 上拿一個(gè)。這里的 Network Poller 是 go 管理網(wǎng)絡(luò)調(diào)用的模塊,如下圖,當(dāng)出現(xiàn)網(wǎng)絡(luò) IO 時(shí),就會(huì)將當(dāng)前的 G 移交到 Network Poller 處理。P.S. Network Poller 的實(shí)現(xiàn),又可以扯一篇文章出來(lái)了,以后有空專(zhuān)門(mén)開(kāi)一篇分析。
src="https://www.isolves.com/d/file/p/2023/09-18/a9690b348185cead9640fed0eb650bd3.jpg" />
G1 正在 M 上運(yùn)行且需要進(jìn)行網(wǎng)絡(luò)調(diào)用了



于是 G1 被挪到 Net Poller 進(jìn)行異步網(wǎng)絡(luò)調(diào)用,現(xiàn)在 M 就可以執(zhí)行 G2 了

Work-stealing 機(jī)制

GMP高性能的關(guān)鍵,在于引入了 work-stealing 機(jī)制,如下圖所示:


work-stealing 機(jī)制

當(dāng)一個(gè)新的 G 被創(chuàng)建時(shí),它會(huì)被追加到它當(dāng)前 P 的 LRQ 隊(duì)尾。當(dāng)一個(gè) G 被執(zhí)行完時(shí),它當(dāng)前的 P 會(huì)先嘗試從自己的 LRQ 中 pop 一個(gè) G 出來(lái)執(zhí)行,如果此時(shí)隊(duì)列為空,則會(huì)隨機(jī)選取一個(gè) P 并偷走它一半的 G ,對(duì)應(yīng)圖中,也就是 3 個(gè) G

這就好比公司里的卷王,自己的需求做完了,還要悄悄把摸魚(yú)大王的需求清單里一半的需求都掛到自己名下,囧

最后一個(gè)疑問(wèn):GRQ 什么時(shí)候被寫(xiě)入呢?

這又涉及到 G 創(chuàng)建時(shí)的分配流程了,我們知道,goroutine 都是由老的 goroutine 分配出來(lái)的,mAIn 函數(shù)也不例外,因此每個(gè) G 被分配的時(shí)候已經(jīng)有一個(gè)老 G 和對(duì)應(yīng)的老 P 了,在掛載 G 到 P 上時(shí),也會(huì)優(yōu)先掛在到老 P 的 LRQ 上去。但是老 P 的 LRQ 其實(shí)是有限的,當(dāng)掛滿的時(shí)候,這個(gè)新 G 就只能掛到 GRQ 上,等待被調(diào)度了。可以參考源碼中 P 的定義,其中 runq 就是 GRQ 了:

type p struct {
   ...
    // Queue of runnable goroutines. Accessed without lock.
   runqhead uint32
   runqtail uint32
   runq     [256]guintptr
    ...
}

5. 小結(jié)

本文講解了 go 語(yǔ)言調(diào)度器的發(fā)展及基于 GMP 的 work-stealing 策略,這個(gè)「偷」可以說(shuō)是非常精妙啦~ 這是 「Golang 并發(fā)編程」 系列的第一篇文章,如果對(duì)你有幫助,可以點(diǎn)個(gè)關(guān)注/在看來(lái)激勵(lì)我繼續(xù)寫(xiě)文章~

分享到:
標(biāo)簽:Golang
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 52000

    網(wǎng)站

  • 12

    小程序

  • 1037587

    文章

  • 756

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定