欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Go語言中調(diào)度器的原理與使用

 更新時(shí)間:2023年07月31日 08:39:59   作者:碼一行  
這篇文章主要介紹了Go語言運(yùn)行時(shí)調(diào)度器的實(shí)現(xiàn)原理,其中包含調(diào)度器的設(shè)計(jì)與實(shí)現(xiàn)原理、演變過程以及與運(yùn)行時(shí)調(diào)度相關(guān)的數(shù)據(jù)結(jié)構(gòu),希望對(duì)大家有所幫助

概述

Go 語言在并發(fā)編程方面有強(qiáng)大的能力,這離不開語言層面對(duì)并發(fā)編程的支持。本節(jié)會(huì)介紹 Go 語言運(yùn)行時(shí)調(diào)度器的實(shí)現(xiàn)原理,其中包含調(diào)度器的設(shè)計(jì)與實(shí)現(xiàn)原理、演變過程以及與運(yùn)行時(shí)調(diào)度相關(guān)的數(shù)據(jù)結(jié)構(gòu)。

談到 Go 語言調(diào)度器,我們繞不開的是操作系統(tǒng)、進(jìn)程與線程這些概念,線程是操作系統(tǒng)調(diào)度時(shí)的最基本單元,而 Linux 在調(diào)度器并不區(qū)分進(jìn)程和線程的調(diào)度,它們?cè)诓煌僮飨到y(tǒng)上也有不同的實(shí)現(xiàn),但是在大多數(shù)的實(shí)現(xiàn)中線程都屬于進(jìn)程:

多個(gè)線程可以屬于同一個(gè)進(jìn)程并共享內(nèi)存空間。因?yàn)槎嗑€程不需要?jiǎng)?chuàng)建新的虛擬內(nèi)存空間,所以它們也不需要內(nèi)存管理單元處理上下文的切換,線程之間的通信也正是基于共享的內(nèi)存進(jìn)行的,與重量級(jí)的進(jìn)程相比,線程顯得比較輕量。

雖然線程比較輕量,但是在調(diào)度時(shí)也有比較大的額外開銷。每個(gè)線程會(huì)都占用 1M 以上的內(nèi)存空間,在切換線程時(shí)不止會(huì)消耗較多的內(nèi)存,恢復(fù)寄存器中的內(nèi)容還需要向操作系統(tǒng)申請(qǐng)或者銷毀資源,每一次線程上下文的切換都需要消耗 ~1us 左右的時(shí)間,但是 Go 調(diào)度器對(duì) Goroutine 的上下文切換約為 ~0.2us,減少了 80% 的額外開銷。

Go 語言的調(diào)度器通過使用與 CPU 數(shù)量相等的線程減少線程頻繁切換的內(nèi)存開銷,同時(shí)在每一個(gè)線程上執(zhí)行額外開銷更低的 Goroutine 來降低操作系統(tǒng)和硬件的負(fù)載。

設(shè)計(jì)原理

今天的 Go 語言調(diào)度器有著優(yōu)異的性能,但是如果我們回頭看 Go 語言的 0.x 版本的調(diào)度器會(huì)發(fā)現(xiàn)最初的調(diào)度器不僅實(shí)現(xiàn)非常簡(jiǎn)陋,也無法支撐高并發(fā)的服務(wù)。調(diào)度器經(jīng)過幾個(gè)大版本的迭代才有今天的優(yōu)異性能,歷史上幾個(gè)不同版本的調(diào)度器引入了不同的改進(jìn),也存在著不同的缺陷:

1.單線程調(diào)度器 — Go 0.x

  • 改進(jìn):只包含 40 多行代碼;
  • 缺陷:程序中只能存在一個(gè)活躍線程,由 G-M 模型組成;

2.多線程調(diào)度器 — Go 1.0

  • 改進(jìn):允許運(yùn)行多線程的程序;
  • 缺陷:全局鎖導(dǎo)致競(jìng)爭(zhēng)嚴(yán)重;

3.任務(wù)竊取調(diào)度器 — Go 1.1

  • 改進(jìn)1:引入了處理器 P,構(gòu)成了目前的 G-M-P 模型;
  • 改進(jìn)2:在處理器 P 的基礎(chǔ)上實(shí)現(xiàn)了基于工作竊取的調(diào)度器;
  • 缺陷1:在某些情況下,Goroutine 不會(huì)讓出線程,進(jìn)而造成饑餓問題;
  • 缺陷2:時(shí)間過長(zhǎng)的垃圾回收(Stop-the-world,STW)會(huì)導(dǎo)致程序長(zhǎng)時(shí)間無法工作;

4..搶占式調(diào)度器 — Go 1.2 ~ 至今

基于協(xié)作的搶占式調(diào)度器 - 1.2 ~ 1.13

  • 改進(jìn):通過編譯器在函數(shù)調(diào)用時(shí)插入搶占檢查指令,在函數(shù)調(diào)用時(shí)檢查當(dāng)前 Goroutine 是否發(fā)起了搶占請(qǐng)求,實(shí)現(xiàn)基于協(xié)作的搶占式調(diào)度;
  • 缺陷:Goroutine 可能會(huì)因?yàn)槔厥蘸脱h(huán)長(zhǎng)時(shí)間占用資源導(dǎo)致程序暫停;

基于信號(hào)的搶占式調(diào)度器 - 1.14 ~ 至今

  • 改進(jìn):實(shí)現(xiàn)基于信號(hào)的真搶占式調(diào)度;
  • 缺陷1:垃圾回收在掃描棧時(shí)會(huì)觸發(fā)搶占調(diào)度;
  • 缺陷2:搶占的時(shí)間點(diǎn)不夠多,還不能覆蓋全部的邊緣情況;

5.非均勻存儲(chǔ)訪問調(diào)度器 — 提案

  • 改進(jìn):對(duì)運(yùn)行時(shí)的各種資源進(jìn)行分區(qū);
  • 缺陷:實(shí)現(xiàn)非常復(fù)雜,到今天還沒有提上日程;

除了多線程、任務(wù)竊取和搶占式調(diào)度器之外,Go 語言社區(qū)目前還有一個(gè)非均勻存儲(chǔ)訪問(Non-uniform memory access,NUMA)調(diào)度器的提案。在這一節(jié)中,我們將依次介紹不同版本調(diào)度器的實(shí)現(xiàn)原理以及未來可能會(huì)實(shí)現(xiàn)的調(diào)度器提案。

單線程調(diào)度器

0.x 版本調(diào)度器只包含兩種結(jié)構(gòu) — 表示 Goroutine 的 G 和表示線程的 M 兩種結(jié)構(gòu),全局也只有一個(gè)線程。我們可以在 clean up scheduler 提交中找到單線程調(diào)度器的源代碼,在這時(shí) Go 語言的調(diào)度器還是由 C 語言實(shí)現(xiàn)的,調(diào)度函數(shù) runtime.scheduler:9682400 也只包含 40 多行代碼 :

static void scheduler(void) {
	G* gp;
	lock(&sched);
	if(gosave(&m->sched)){
		lock(&sched);
		gp = m->curg;
		switch(gp->status){
		case Grunnable:
		case Grunning:
			gp->status = Grunnable;
			gput(gp);
			break;
		...
		}
		notewakeup(&gp->stopped);
	}
	gp = nextgandunlock();
	noteclear(&gp->stopped);
	gp->status = Grunning;
	m->curg = gp;
	g = gp;
	gogo(&gp->sched);
}

該函數(shù)會(huì)遵循如下的過程調(diào)度 Goroutine:

  • 獲取調(diào)度器的全局鎖;
  • 調(diào)用 runtime.gosave:9682400 保存棧寄存器和程序計(jì)數(shù)器;
  • 調(diào)用 runtime.nextgandunlock:9682400 獲取下一個(gè)需要運(yùn)行的 Goroutine 并解鎖調(diào)度器;
  • 修改全局線程 m 上要執(zhí)行的 Goroutine;
  • 調(diào)用 runtime.gogo:9682400 函數(shù)運(yùn)行最新的 Goroutine;

雖然這個(gè)單線程調(diào)度器的唯一優(yōu)點(diǎn)就是能運(yùn)行,但是這次提交已經(jīng)包含了 G 和 M 兩個(gè)重要的數(shù)據(jù)結(jié)構(gòu),也建立了 Go 語言調(diào)度器的框架。

多線程調(diào)度器

Go 語言在 1.0 版本正式發(fā)布時(shí)就支持了多線程的調(diào)度器,與上一個(gè)版本幾乎不可用的調(diào)度器相比,Go 語言團(tuán)隊(duì)在這一階段實(shí)現(xiàn)了從不可用到可用的跨越。我們可以在 pkg/runtime/proc.c 文件中找到 1.0.1 版本的調(diào)度器,多線程版本的調(diào)度函數(shù) runtime.schedule:go1.0.1 包含 70 多行代碼,我們?cè)谶@里保留了該函數(shù)的核心邏輯:

static void schedule(G *gp) {
	schedlock();
	if(gp != nil) {
		gp->m = nil;
		uint32 v = runtime·xadd(&runtime·sched.atomic, -1<<mcpuShift);
		if(atomic_mcpu(v) > maxgomaxprocs)
			runtime·throw("negative mcpu in scheduler");
		switch(gp->status){
		case Grunning:
			gp->status = Grunnable;
			gput(gp);
			break;
		case ...:
		}
	} else {
		...
	}
	gp = nextgandunlock();
	gp->status = Grunning;
	m->curg = gp;
	gp->m = m;
	runtime·gogo(&gp->sched, 0);
}

整體的邏輯與單線程調(diào)度器沒有太多區(qū)別,因?yàn)槲覀兊某绦蛑锌赡芡瑫r(shí)存在多個(gè)活躍線程,所以多線程調(diào)度器引入了 GOMAXPROCS 變量幫助我們靈活控制程序中的最大處理器數(shù),即活躍線程數(shù)。

多線程調(diào)度器的主要問題是調(diào)度時(shí)的鎖競(jìng)爭(zhēng)會(huì)嚴(yán)重浪費(fèi)資源,Scalable Go Scheduler Design Doc 中對(duì)調(diào)度器做的性能測(cè)試發(fā)現(xiàn) 14% 的時(shí)間都花費(fèi)在 runtime.futex:go1.0.1 上,該調(diào)度器有以下問題需要解決:

  • 調(diào)度器和鎖是全局資源,所有的調(diào)度狀態(tài)都是中心化存儲(chǔ)的,鎖競(jìng)爭(zhēng)問題嚴(yán)重;
  • 線程需要經(jīng)常互相傳遞可運(yùn)行的 Goroutine,引入了大量的延遲;
  • 每個(gè)線程都需要處理內(nèi)存緩存,導(dǎo)致大量的內(nèi)存占用并影響數(shù)據(jù)局部性;
  • 系統(tǒng)調(diào)用頻繁阻塞和解除阻塞正在運(yùn)行的線程,增加了額外開銷;

這里的全局鎖問題和 Linux 操作系統(tǒng)調(diào)度器在早期遇到的問題比較相似,解決的方案也都大同小異。

任務(wù)竊取調(diào)度器

2012 年 Google 的工程師 Dmitry Vyukov 在 Scalable Go Scheduler Design Doc 中指出了現(xiàn)有多線程調(diào)度器的問題并在多線程調(diào)度器上提出了兩個(gè)改進(jìn)的手段:

  • 在當(dāng)前的 G-M 模型中引入了處理器 P,增加中間層;
  • 在處理器 P 的基礎(chǔ)上實(shí)現(xiàn)基于工作竊取的調(diào)度器;

基于任務(wù)竊取的 Go 語言調(diào)度器使用了沿用至今的 G-M-P 模型,我們能在 runtime: improved scheduler 提交中找到任務(wù)竊取調(diào)度器剛被實(shí)現(xiàn)時(shí)的源代碼,調(diào)度器的 runtime.schedule:779c45a 在這個(gè)版本的調(diào)度器中反而更簡(jiǎn)單了:

static void schedule(void) {
    G *gp;
 top:
    if(runtime·gcwaiting) {
        gcstopm();
        goto top;
    }
    gp = runqget(m->p);
    if(gp == nil)
        gp = findrunnable();
    ...
    execute(gp);
}
  • 如果當(dāng)前運(yùn)行時(shí)在等待垃圾回收,調(diào)用 runtime.gcstopm:779c45a 函數(shù);
  • 調(diào)用 runtime.runqget:779c45a 和 runtime.findrunnable:779c45a 從本地或者全局的運(yùn)行隊(duì)列中獲取待執(zhí)行的 Goroutine;
  • 調(diào)用 runtime.execute:779c45a 在當(dāng)前線程 M 上運(yùn)行 Goroutine;

當(dāng)前處理器本地的運(yùn)行隊(duì)列中不包含 Goroutine 時(shí),調(diào)用 runtime.findrunnable:779c45a 會(huì)觸發(fā)工作竊取,從其它的處理器的隊(duì)列中隨機(jī)獲取一些 Goroutine。

運(yùn)行時(shí) G-M-P 模型中引入的處理器 P 是線程和 Goroutine 的中間層,我們從它的結(jié)構(gòu)體中就能看到處理器與 M 和 G 的關(guān)系:

struct P {
	Lock;
	uint32	status;
	P*	link;
	uint32	tick;
	M*	m;
	MCache*	mcache;
	G**	runq;
	int32	runqhead;
	int32	runqtail;
	int32	runqsize;
	G*	gfree;
	int32	gfreecnt;
};

處理器持有一個(gè)由可運(yùn)行的 Goroutine 組成的環(huán)形的運(yùn)行隊(duì)列 runq,還反向持有一個(gè)線程。調(diào)度器在調(diào)度時(shí)會(huì)從處理器的隊(duì)列中選擇隊(duì)列頭的 Goroutine 放到線程 M 上執(zhí)行。

基于工作竊取的多線程調(diào)度器將每一個(gè)線程綁定到了獨(dú)立的 CPU 上,這些線程會(huì)被不同處理器管理,不同的處理器通過工作竊取對(duì)任務(wù)進(jìn)行再分配實(shí)現(xiàn)任務(wù)的平衡,也能提升調(diào)度器和 Go 語言程序的整體性能,今天所有的 Go 語言服務(wù)都受益于這一改動(dòng)。

搶占式調(diào)度器

對(duì) Go 語言并發(fā)模型的修改提升了調(diào)度器的性能,但是 1.1 版本中的調(diào)度器仍然不支持搶占式調(diào)度,程序只能依靠 Goroutine 主動(dòng)讓出 CPU 資源才能觸發(fā)調(diào)度。Go 語言的調(diào)度器在 1.2 版本中引入基于協(xié)作的搶占式調(diào)度解決下面的問題:

  • 某些 Goroutine 可以長(zhǎng)時(shí)間占用線程,造成其它 Goroutine 的饑餓;
  • 垃圾回收需要暫停整個(gè)程序(Stop-the-world,STW),最長(zhǎng)可能需要幾分鐘的時(shí)間,導(dǎo)致整個(gè)程序無法工作;

1.2 版本的搶占式調(diào)度雖然能夠緩解這個(gè)問題,但是它實(shí)現(xiàn)的搶占式調(diào)度是基于協(xié)作的,在之后很長(zhǎng)的一段時(shí)間里 Go 語言的調(diào)度器都有一些無法被搶占的邊緣情況,例如:for 循環(huán)或者垃圾回收長(zhǎng)時(shí)間占用線程,這些問題中的一部分直到 1.14 才被基于信號(hào)的搶占式調(diào)度解決。

基于協(xié)作的搶占式調(diào)度

我們可以在 pkg/runtime/proc.c 文件中找到引入基于協(xié)作的搶占式調(diào)度后的調(diào)度器。Go 語言會(huì)在分段棧的機(jī)制上實(shí)現(xiàn)搶占調(diào)度,利用編譯器在分段棧上插入的函數(shù),所有 Goroutine 在函數(shù)調(diào)用時(shí)都有機(jī)會(huì)進(jìn)入運(yùn)行時(shí)檢查是否需要執(zhí)行搶占。Go 團(tuán)隊(duì)通過以下的多個(gè)提交實(shí)現(xiàn)該特性:

runtime: add stackguard0 to G

為 Goroutine 引入 stackguard0 字段,該字段被設(shè)置成 StackPreempt 意味著當(dāng)前 Goroutine 發(fā)出了搶占請(qǐng)求;

runtime: introduce preemption function (not used for now)

  • 引入搶占函數(shù) runtime.preemptone:1e112cd 和 runtime.preemptall:1e112cd,這兩個(gè)函數(shù)會(huì)改變 Goroutine 的 stackguard0 字段發(fā)出搶占請(qǐng)求;
  • 定義搶占請(qǐng)求 StackPreempt;

runtime: preempt goroutines for GC

  • 在 runtime.stoptheworld:1e112cd 中調(diào)用 runtime.preemptall:1e112cd 設(shè)置所有處理器上正在運(yùn)行的 Goroutine 的 stackguard0 為 StackPreempt;
  • 在 runtime.newstack:1e112cd 中增加搶占的代碼,當(dāng) stackguard0 等于 StackPreempt 時(shí)觸發(fā)調(diào)度器搶占讓出線程;

runtime: preempt long-running goroutines

在系統(tǒng)監(jiān)控中,如果一個(gè) Goroutine 的運(yùn)行時(shí)間超過 10ms,就會(huì)調(diào)用 runtime.retake:1e112cd 和 runtime.preemptone:1e112cd

runtime: more reliable preemption

修復(fù) Goroutine 因?yàn)橹芷谛詧?zhí)行非阻塞的 CGO 或者系統(tǒng)調(diào)用不會(huì)被搶占的問題;

上面的多個(gè)提交實(shí)現(xiàn)了搶占式調(diào)度,但是還缺少最關(guān)鍵的一個(gè)環(huán)節(jié) — 編譯器如何在函數(shù)調(diào)用前插入函數(shù),我們能在非常古老的提交 runtime: stack growth adjustments, cleanup 中找到編譯器插入函數(shù)的雛形,最新版本的 Go 語言會(huì)通過 cmd/internal/obj/x86.stacksplit 插入 runtime.morestack,該函數(shù)可能會(huì)調(diào)用 runtime.newstack 觸發(fā)搶占。從上面的多個(gè)提交中,我們能歸納出基于協(xié)作的搶占式調(diào)度的工作原理:

  • 編譯器會(huì)在調(diào)用函數(shù)前插入 runtime.morestack;
  • Go 語言運(yùn)行時(shí)會(huì)在垃圾回收暫停程序、系統(tǒng)監(jiān)控發(fā)現(xiàn) Goroutine 運(yùn)行超過 10ms 時(shí)發(fā)出搶占請(qǐng)求 StackPreempt
  • 當(dāng)發(fā)生函數(shù)調(diào)用時(shí),可能會(huì)執(zhí)行編譯器插入的 runtime.morestack,它調(diào)用的 runtime.newstack 會(huì)檢查 Goroutine 的 stackguard0 字段是否為 StackPreempt;
  • 如果 stackguard0 是 StackPreempt,就會(huì)觸發(fā)搶占讓出當(dāng)前線程;

這種實(shí)現(xiàn)方式雖然增加了運(yùn)行時(shí)的復(fù)雜度,但是實(shí)現(xiàn)相對(duì)簡(jiǎn)單,也沒有帶來過多的額外開銷,總體來看還是比較成功的實(shí)現(xiàn),也在 Go 語言中使用了 10 幾個(gè)版本。因?yàn)檫@里的搶占是通過編譯器插入函數(shù)實(shí)現(xiàn)的,還是需要函數(shù)調(diào)用作為入口才能觸發(fā)搶占,所以這是一種協(xié)作式的搶占式調(diào)度。

基于信號(hào)的搶占式調(diào)度

基于協(xié)作的搶占式調(diào)度雖然實(shí)現(xiàn)巧妙,但是并不完備,我們能在 runtime: non-cooperative goroutine preemption 中找到一些遺留問題:

  • runtime: tight loops should be preemptible
  • An empty for{} will block large slice allocation in another goroutine, even with GOMAXPROCS > 1 ?
  • runtime: tight loop hangs process completely after some time

Go 語言在 1.14 版本中實(shí)現(xiàn)了非協(xié)作的搶占式調(diào)度,在實(shí)現(xiàn)的過程中我們重構(gòu)已有的邏輯并為 Goroutine 增加新的狀態(tài)和字段來支持搶占。Go 團(tuán)隊(duì)通過下面的一系列提交實(shí)現(xiàn)了這一功能,我們可以按時(shí)間順序分析相關(guān)提交理解它的工作原理:

runtime: add general suspendG/resumeG

  • 掛起 Goroutine 的過程是在垃圾回收的棧掃描時(shí)完成的,我們通過 runtime.suspendG 和 runtime.resumeG 兩個(gè)函數(shù)重構(gòu)棧掃描這一過程;
  • 調(diào)用 runtime.suspendG 時(shí)會(huì)將處于運(yùn)行狀態(tài)的 Goroutine 的 preemptStop 標(biāo)記成 true;
  • 調(diào)用 runtime.preemptPark 可以掛起當(dāng)前 Goroutine、將其狀態(tài)更新成 _Gpreempted 并觸發(fā)調(diào)度器的重新調(diào)度,該函數(shù)能夠交出線程控制權(quán);

runtime: asynchronous preemption function for x86

在 x86 架構(gòu)上增加異步搶占的函數(shù) runtime.asyncPreempt 和 runtime.asyncPreempt2;

runtime: use signals to preempt Gs for suspendG

  • 支持通過向線程發(fā)送信號(hào)的方式暫停運(yùn)行的 Goroutine;
  • 在 runtime.sighandler 函數(shù)中注冊(cè) SIGURG 信號(hào)的處理函數(shù) runtime.doSigPreempt;
  • 實(shí)現(xiàn) runtime.preemptM,它可以通過 SIGURG 信號(hào)向線程發(fā)送搶占請(qǐng)求;

runtime: implement async scheduler preemption

修改 runtime.preemptone 函數(shù)的實(shí)現(xiàn),加入異步搶占的邏輯;

目前的搶占式調(diào)度也只會(huì)在垃圾回收掃描任務(wù)時(shí)觸發(fā),我們可以梳理一下上述代碼實(shí)現(xiàn)的搶占式調(diào)度過程:

1.程序啟動(dòng)時(shí),在 runtime.sighandler 中注冊(cè) SIGURG 信號(hào)的處理函數(shù) runtime.doSigPreempt;

2.在觸發(fā)垃圾回收的棧掃描時(shí)會(huì)調(diào)用 runtime.suspendG 掛起 Goroutine,該函數(shù)會(huì)執(zhí)行下面的邏輯:

將 _Grunning 狀態(tài)的 Goroutine 標(biāo)記成可以被搶占,即將 preemptStop 設(shè)置成 true;

調(diào)用 runtime.preemptM 觸發(fā)搶占;

3.runtime.preemptM會(huì)調(diào)用 runtime.signalM 向線程發(fā)送信號(hào) SIGURG;

4.操作系統(tǒng)會(huì)中斷正在運(yùn)行的線程并執(zhí)行預(yù)先注冊(cè)的信號(hào)處理函數(shù) runtime.doSigPreempt;

5.runtime.doSigPreempt 函數(shù)會(huì)處理搶占信號(hào),獲取當(dāng)前的 SP 和 PC 寄存器并調(diào)用 runtime.sigctxt.pushCall;

6.runtime.sigctxt.pushCall 會(huì)修改寄存器并在程序回到用戶態(tài)時(shí)執(zhí)行 runtime.asyncPreempt;

7.匯編指令 runtime.asyncPreempt會(huì)調(diào)用運(yùn)行時(shí)函數(shù) runtime.asyncPreempt2;

8.runtime.asyncPreempt2 會(huì)調(diào)用 runtime.preemptPark

9.runtime.preemptPark 會(huì)修改當(dāng)前 Goroutine 的狀態(tài)到 _Gpreempted 并調(diào)用 runtime.schedule 讓當(dāng)前函數(shù)陷入休眠并讓出線程,調(diào)度器會(huì)選擇其它的 Goroutine 繼續(xù)執(zhí)行;

上述 9 個(gè)步驟展示了基于信號(hào)的搶占式調(diào)度的執(zhí)行過程。除了分析搶占的過程之外,我們還需要討論一下?lián)屨夹盘?hào)的選擇,提案根據(jù)以下的四個(gè)原因選擇 SIGURG 作為觸發(fā)異步搶占的信號(hào);

  • 該信號(hào)需要被調(diào)試器透?jìng)鳎?/li>
  • 該信號(hào)不會(huì)被內(nèi)部的 libc 庫使用并攔截;
  • 該信號(hào)可以隨意出現(xiàn)并且不觸發(fā)任何后果;
  • 我們需要處理多個(gè)平臺(tái)上的不同信號(hào);

STW 和棧掃描是一個(gè)可以搶占的安全點(diǎn)(Safe-points),所以 Go 語言會(huì)在這里先加入搶占功能?;谛盘?hào)的搶占式調(diào)度只解決了垃圾回收和棧掃描時(shí)存在的問題,它到目前為止沒有解決所有問題,但是這種真搶占式調(diào)度是調(diào)度器走向完備的開始,相信在未來我們會(huì)在更多的地方觸發(fā)搶占。

非均勻內(nèi)存訪問調(diào)度器

非均勻內(nèi)存訪問(Non-uniform memory access,NUMA)調(diào)度器現(xiàn)在只是 Go 語言的提案。該提案的原理就是通過拆分全局資源,讓各個(gè)處理器能夠就近獲取,減少鎖競(jìng)爭(zhēng)并增加數(shù)據(jù)的局部性。

在目前的運(yùn)行時(shí)中,線程、處理器、網(wǎng)絡(luò)輪詢器、運(yùn)行隊(duì)列、全局內(nèi)存分配器狀態(tài)、內(nèi)存分配緩存和垃圾收集器都是全局資源。運(yùn)行時(shí)沒有保證本地化,也不清楚系統(tǒng)的拓?fù)浣Y(jié)構(gòu),部分結(jié)構(gòu)可以提供一定的局部性,但是從全局來看沒有這種保證。

如上圖所示,堆棧、全局運(yùn)行隊(duì)列和線程池會(huì)按照 NUMA 節(jié)點(diǎn)進(jìn)行分區(qū),網(wǎng)絡(luò)輪詢器和計(jì)時(shí)器會(huì)由單獨(dú)的處理器持有。這種方式雖然能夠利用局部性提高調(diào)度器的性能,但是本身的實(shí)現(xiàn)過于復(fù)雜,所以 Go 語言團(tuán)隊(duì)還沒有著手實(shí)現(xiàn)這一提案。

小結(jié)

Go 語言的調(diào)度器在最初的幾個(gè)版本中迅速迭代,但是從 1.2 版本之后調(diào)度器就沒有太多的變化,直到 1.14 版本引入了真正的搶占式調(diào)度才解決了自 1.2 以來一直存在的問題。在可預(yù)見的未來,Go 語言的調(diào)度器還會(huì)進(jìn)一步演進(jìn),增加觸發(fā)搶占式調(diào)度的時(shí)間點(diǎn)以減少存在的邊緣情況。

以上就是詳解Go語言中調(diào)度器的原理與使用的詳細(xì)內(nèi)容,更多關(guān)于Go語言調(diào)度器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go語言運(yùn)算符案例講解

    Go語言運(yùn)算符案例講解

    這篇文章主要介紹了Go語言運(yùn)算符案例講解,本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-07-07
  • Go?Web開發(fā)之Gin多服務(wù)配置及優(yōu)雅關(guān)閉平滑重啟實(shí)現(xiàn)方法

    Go?Web開發(fā)之Gin多服務(wù)配置及優(yōu)雅關(guān)閉平滑重啟實(shí)現(xiàn)方法

    這篇文章主要為大家介紹了Go?Web開發(fā)之Gin多服務(wù)配置及優(yōu)雅關(guān)閉平滑重啟實(shí)現(xiàn)方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • Go語言中如何確保Cookie數(shù)據(jù)的安全傳輸

    Go語言中如何確保Cookie數(shù)據(jù)的安全傳輸

    這篇文章主要介紹了Go語言中如何確保Cookie數(shù)據(jù)的安全傳輸,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • Golang中struct{}和struct{}{}的區(qū)別解析

    Golang中struct{}和struct{}{}的區(qū)別解析

    這篇文章主要介紹了Golang中struct{}和struct{}{}的區(qū)別,通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-03-03
  • Golang語言如何高效拼接字符串詳解

    Golang語言如何高效拼接字符串詳解

    最近在做性能優(yōu)化,有個(gè)函數(shù)里面的耗時(shí)特別長(zhǎng),看里面的操作大多是一些字符串拼接的操作,而字符串拼接在 golang 里面其實(shí)有很多種實(shí)現(xiàn),下面這篇文章主要給大家介紹了關(guān)于Golang語言如何高效拼接字符串的相關(guān)資料,需要的朋友可以參考下
    2021-11-11
  • 一文搞懂Golang 時(shí)間和日期相關(guān)函數(shù)

    一文搞懂Golang 時(shí)間和日期相關(guān)函數(shù)

    這篇文章主要介紹了Golang 時(shí)間和日期相關(guān)函數(shù),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-12-12
  • goland 實(shí)現(xiàn)websocket server的示例代碼

    goland 實(shí)現(xiàn)websocket server的示例代碼

    本文主要介紹了goland 實(shí)現(xiàn)websocket server的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • Golang使用lua腳本實(shí)現(xiàn)redis原子操作

    Golang使用lua腳本實(shí)現(xiàn)redis原子操作

    這篇文章主要介紹了Golang使用lua腳本實(shí)現(xiàn)redis原子操作,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的工作或?qū)W習(xí)具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-03-03
  • golang整合日志zap的實(shí)現(xiàn)示例

    golang整合日志zap的實(shí)現(xiàn)示例

    Go語言中的zap庫提供了強(qiáng)大的日志管理功能,支持日志記錄到文件、日志切割、多日志級(jí)別、結(jié)構(gòu)化日志輸出等,它通過三種方法zap.NewProduction()、zap.NewDevelopment()和zap.NewExample(),快速構(gòu)建適用于不同環(huán)境的logger,感興趣的可以了解一下
    2024-10-10
  • Go語言使用goroutine及通道實(shí)現(xiàn)并發(fā)詳解

    Go語言使用goroutine及通道實(shí)現(xiàn)并發(fā)詳解

    這篇文章主要為大家介紹了Go語言使用goroutine及通道實(shí)現(xiàn)并發(fā)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08

最新評(píng)論