W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
cgo內(nèi)部實(shí)現(xiàn)相關(guān)的知識(shí)是比較偏底層的,同時(shí)與Go系統(tǒng)調(diào)用約定以及的goroutine的調(diào)度都有一定的關(guān)聯(lián),因此這里先寫一些預(yù)備知識(shí)。
本節(jié)的內(nèi)容可能需要前面第三章和第五章的一些基礎(chǔ),同時(shí)也作為前面沒有提到的一些細(xì)節(jié)的繼續(xù)補(bǔ)充。
Go的運(yùn)行時(shí)庫中使用了幾個(gè)重要的結(jié)構(gòu)體,其中M是機(jī)器的抽象。每個(gè)M可以運(yùn)行各個(gè)goroutine,在結(jié)構(gòu)體M的定義中有一個(gè)相對(duì)特殊的goroutine叫g(shù)0(還有另一個(gè)比較特殊的gsignal,與本節(jié)內(nèi)容無關(guān)暫且不講)。那么這個(gè)g0特殊在什么地方呢?
g0的特殊之處在于它是帶有調(diào)度棧的goroutine,下文就將其稱為“m的g0棧“。Go在執(zhí)行調(diào)度相關(guān)代碼時(shí),都是使用的m的g0棧。當(dāng)一個(gè)g執(zhí)行的是調(diào)度相關(guān)的代碼時(shí),它并不是直接在自己的棧中執(zhí)行,而是先切換到m的g0棧然后再執(zhí)行代碼。
m的g0棧是一個(gè)特殊的棧,g0的分配和普通goroutine的分配過程不同,g0是在m建立時(shí)就生成的,并且給它分配的棧空間比較大,可以假定它的大小是足夠大而不必使用分段棧。而普通的goroutine是在runtime.newproc時(shí)建立,并且初始??臻g分配得很小(4K),會(huì)在需要時(shí)增長。不僅如此,m的g0棧同時(shí)也是這個(gè)m對(duì)應(yīng)的物理線程的棧。
這樣就相當(dāng)于擁有了一個(gè)“無窮”大小的非分段棧,于是回答了前面提的那個(gè)問題:Go使用的是分段棧,初始棧大小很小,當(dāng)發(fā)現(xiàn)棧不夠時(shí)會(huì)動(dòng)態(tài)增長。動(dòng)態(tài)增長是通過進(jìn)入函數(shù)時(shí)插入檢測(cè)指令實(shí)現(xiàn)的。然而C函數(shù)不使用分段棧技術(shù),并且假設(shè)棧是足夠大的。調(diào)用cgo代碼時(shí),使用的是m的g0棧,這是一個(gè)足夠大的不會(huì)發(fā)生分段的棧。
函數(shù)newm是新那一個(gè)結(jié)構(gòu)體M,其中調(diào)用runtime.allocm分配M的空間。它的g0域是這個(gè)分配的:
mp->g0 = runtime·malg(8192);
等等!好像有哪里不對(duì)?這個(gè)棧并不是真正的“無窮”大的,它只有8K并且不會(huì)增長?那么如果調(diào)用的C函數(shù)使用超過8K的棧大小會(huì)發(fā)生什么事情呢?讓我們先試一下,我們建立一個(gè)文件test.go,內(nèi)容如下:
package main
/*
#include "stdio.h"
void test(int n) {
char dummy[1024];
printf("in c test func iterator %d\n", n);
if(n <= 0) {
return;
}
dummy[n] = '\a';
test(n-1);
}
#cgo CFLAGS: -g
*/
import "C"
func main() {
C.test(C.int(20))
}
函數(shù)test被遞歸調(diào)用多次之后,使用的??臻g是超過8K的。然后?程序運(yùn)行正常,什么也沒發(fā)生。為什么呢?先賣個(gè)關(guān)子,到后面再解釋原因。
Go的運(yùn)行時(shí)庫對(duì)系統(tǒng)調(diào)用作了特殊處理,所有涉及到調(diào)用系統(tǒng)調(diào)用之前,都會(huì)先調(diào)用runtime.entersyscall,而在出系統(tǒng)調(diào)用函數(shù)之后,會(huì)調(diào)用runtime.exitsyscall。這樣做原因跟調(diào)度器相關(guān),目的是始終維持GOMAXPROCS的數(shù)量,當(dāng)進(jìn)入到系統(tǒng)調(diào)用時(shí),runtime.entersyscall會(huì)將P的M剝離并將它設(shè)置為PSyscall狀態(tài),告知系統(tǒng)此時(shí)其它的P有機(jī)會(huì)運(yùn)行,以保證始終是GOMAXPROCS個(gè)P在運(yùn)行。
runtime.entersyscall函數(shù)會(huì)立刻返回,它僅僅是起到一個(gè)通知的作用。那么這跟cgo又有什么關(guān)系呢?這個(gè)關(guān)系可大著呢!在執(zhí)行cgo函數(shù)調(diào)用之前,其實(shí)系統(tǒng)會(huì)先調(diào)用runtime.entersyscall。這是一個(gè)很關(guān)鍵的處理,Go把cgo的C函數(shù)調(diào)用像系統(tǒng)調(diào)用一樣獨(dú)立出去了,不讓它影響運(yùn)行時(shí)庫。這就回答了前面提出的第二個(gè)問題:Go中的goroutine都是協(xié)作式的,運(yùn)行到調(diào)用runtime庫時(shí)就有機(jī)會(huì)進(jìn)行調(diào)度。然而C函數(shù)是不會(huì)與Go的runtime做這種交互的,所以cgo的函數(shù)不是一個(gè)協(xié)作式的,那么如何避免進(jìn)入C函數(shù)的這個(gè)goroutine“失控”?答案就在這里。將C函數(shù)像處理系統(tǒng)調(diào)用一樣隔離開來,這個(gè)goroutine也就不必參與調(diào)度了。而其它部分的goroutine正常的運(yùn)行不受影響。
退出系統(tǒng)調(diào)用跟進(jìn)入系統(tǒng)調(diào)用是一個(gè)相反的過程,runtime.exitsyscall函數(shù)會(huì)查看當(dāng)前仍然有可用的P,則讓它繼續(xù)運(yùn)行,否則這個(gè)goroutine就要被掛起了。
對(duì)于cgo的代碼也是同樣的作用,出了cgo的C函數(shù)調(diào)用之后會(huì)調(diào)用runtime.exitsyscall。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: