Go1.15.3runtime中的hacking文件

這篇文章的目的是闡明在Go運(yùn)行時(shí)編程與編寫(xiě)普通Go的不同之處。它側(cè)重于普遍的概念,而不是特定接口的細(xì)節(jié)。

Scheduler structures(調(diào)度程序結(jié)構(gòu))

調(diào)度程序管理運(yùn)行時(shí)使用的三種資源:Gs、Ms和Ps。即使您不使用調(diào)度程序,理解這些資源也很重要。

Gs, Ms, Ps

G 簡(jiǎn)單來(lái)說(shuō)就是 goroutine.它由類(lèi)型g表示。當(dāng)一個(gè)goroutine退出時(shí),它的g對(duì)象被返回到一個(gè)空閑g的池中,以后可以被其他goroutine重用。

一個(gè)“M”是一個(gè)操作系統(tǒng)線程,它可以執(zhí)行用戶(hù)Go代碼,運(yùn)行時(shí)代碼、系統(tǒng)調(diào)用或空閑。 它是由類(lèi)型 m表示. 一次可以有任意數(shù)量的Ms,因?yàn)槿我鈹?shù)量的線程可能在系統(tǒng)調(diào)用中被阻塞。

最后,“P”表示執(zhí)行用戶(hù)Go代碼所需的資源,如調(diào)度程序和內(nèi)存分配器狀態(tài)。它由類(lèi)型 p表示。這里可以有 GOMAXPROCS 個(gè)Ps. P可以被認(rèn)為是OS調(diào)度器中的一個(gè)CPU,而“P”類(lèi)型的內(nèi)容則類(lèi)似于每個(gè)CPU的狀態(tài)。 這是放置需要進(jìn)行分片以提高效率的狀態(tài)的好地方, but doesn`t need to be per-thread or
per-goroutine.

調(diào)度器的工作是匹配G(要執(zhí)行的代碼)、M(要執(zhí)行代碼的位置)和P(執(zhí)行代碼的權(quán)限和資源)。當(dāng)M停止執(zhí)行用戶(hù)Go代碼時(shí),例如通過(guò)輸入一個(gè)系統(tǒng)調(diào)用,它將它的P返回到空閑P池。為了恢復(fù)執(zhí)行用戶(hù)Go代碼,例如從系統(tǒng)調(diào)用返回時(shí),它必須從空閑池獲取一個(gè)P。

所有 g 、 mp 對(duì)象都是堆分配的,但從不釋放,所以它們的內(nèi)存保持類(lèi)型穩(wěn)定。因此,runtime可以避免在調(diào)度器的深度出現(xiàn)寫(xiě)障礙。

User stacks and system stacks(用戶(hù)堆棧和系統(tǒng)堆棧)

每個(gè)存活的G都有一個(gè)與之相關(guān)聯(lián)的用戶(hù)堆棧,這是用戶(hù)Go代碼執(zhí)行的地方。用戶(hù)堆棧開(kāi)始很小(例如,2K),并動(dòng)態(tài)增長(zhǎng)或收縮。

每個(gè)M都有一個(gè)與之關(guān)聯(lián)的系統(tǒng)堆棧(也稱(chēng)為M的“g0”堆棧,因?yàn)樗亲鳛榇娓鵊實(shí)現(xiàn)的),在Unix平臺(tái)上,還有一個(gè)信號(hào)堆棧(也稱(chēng)為M的“gsignal”堆棧)。系統(tǒng)和信號(hào)堆棧不能增長(zhǎng),但足夠大,可以執(zhí)行運(yùn)行時(shí)和cgo代碼(8K純Go二進(jìn)制;系統(tǒng)分配的acgo二進(jìn)制)。

運(yùn)行時(shí)代碼經(jīng)常臨時(shí)切換到使用systemstackmcall ,或 asmcgocall 的系統(tǒng)堆棧,以執(zhí)行任務(wù),必須不被搶占,必須不增長(zhǎng)用戶(hù)堆棧,或切換用戶(hù)goroutines。運(yùn)行在系統(tǒng)堆棧上的代碼是隱式不可搶占的,垃圾收集器不會(huì)掃描系統(tǒng)堆棧。在系統(tǒng)堆棧上運(yùn)行時(shí),當(dāng)前用戶(hù)堆棧不用于執(zhí)行。

getg() and getg().m.curg

獲取當(dāng)前用戶(hù) g ,使用 getg().m.curg。

getg()單獨(dú)返回當(dāng)前g,但當(dāng)在系統(tǒng)或信號(hào)堆棧上執(zhí)行時(shí),將分別返回當(dāng)前M的g0gsignal。這通常不是你想要的。

要確定您是在用戶(hù)堆棧上運(yùn)行還是在系統(tǒng)堆棧上運(yùn)行,使用 getg() == getg().m.curg。

Error handling and reporting(錯(cuò)誤處理和報(bào)告)

在用戶(hù)代碼中可以合理恢復(fù)的錯(cuò)誤應(yīng)該像往常一樣使用panic。然而,在某些情況下,panic 會(huì)立即導(dǎo)致致命錯(cuò)誤,例如在系統(tǒng)堆棧上調(diào)用時(shí)或在mallocgc期間調(diào)用時(shí)。

運(yùn)行時(shí)中的大多數(shù)錯(cuò)誤是不可恢復(fù)的。對(duì)于這些情況,使用throw,它轉(zhuǎn)儲(chǔ)回溯并立即終止進(jìn)程。一般來(lái)說(shuō),throw 應(yīng)該傳遞一個(gè)字符串常量,以避免在危險(xiǎn)的情況下進(jìn)行分配。按照慣例,在 throw 之前使用 printprintln 打印額外的詳細(xì)信息,消息前綴為runtime: 。

對(duì)于運(yùn)行時(shí)錯(cuò)誤調(diào)試,使用 GOTRACEBACK=systemGOTRACEBACK=crash 運(yùn)行是有用的。

Synchronization(同步)

runtime 具有多個(gè)同步機(jī)制。它們?cè)谡Z(yǔ)義上存在差異,特別是它們是與goroutine調(diào)度程序交互還是與OS調(diào)度程序交互。

最簡(jiǎn)單的是mutex,它使用lockunlock操作。這應(yīng)該用于在短時(shí)間內(nèi)保護(hù)共享結(jié)構(gòu)。在互斥鎖上的阻塞直接阻塞M,不與Go調(diào)度程序交互。這意味著從運(yùn)行時(shí)的最低級(jí)別使用它是安全的,但還可以防止任何相關(guān)的G和P被重新調(diào)度。rwmutex是相似的。

對(duì)于一次性通知,使用note,它提供notesleepnotewakeup。與傳統(tǒng)的UNIX sleep / wakeup不同,note是沒(méi)有競(jìng)爭(zhēng)的,所以如果notwakeup已經(jīng)發(fā)生,notesleep立即返回。使用noteclear后,note可以被重置,它不能與sleep或wake - up競(jìng)爭(zhēng)。像互斥,阻止注意塊M .然而,有不同的方法可以睡在一個(gè)“注意”:notesleep還能防止任何相關(guān)的延期G和P,而notetsleepg就像一個(gè)阻塞的系統(tǒng)調(diào)用,允許重用運(yùn)行另一個(gè)P G .這仍然是低效率比直接阻斷G,因?yàn)樗牧艘粋€(gè)M。

要直接與goroutine調(diào)度程序交互,使用goparkgoready。gopark將當(dāng)前goroutine置于等待狀態(tài),并將其從調(diào)度程序的運(yùn)行隊(duì)列中刪除,然后在當(dāng)前的M/P上調(diào)度另一個(gè)goroutine。goready將一個(gè)停放的goroutine恢復(fù)到“可運(yùn)行”狀態(tài),并將其添加到運(yùn)行隊(duì)列中。

總之,

<table>
<tr><th></th><th colspan="3">Blocks</th></tr>
<tr><th>Interface</th><th>G</th><th>M</th><th>P</th></tr>
<tr><td>(rw)mutex</td><td>Y</td><td>Y</td><td>Y</td></tr>
<tr><td>note</td><td>Y</td><td>Y</td><td>Y/N</td></tr>
<tr><td>park</td><td>Y</td><td>N</td><td>N</td></tr>
</table>

Atomics(原子)

運(yùn)行時(shí)在“runtime/internal/atomic”中使用自己的atomics包。這對(duì)應(yīng)于sync/atomic,但由于歷史原因,函數(shù)有不同的名稱(chēng),運(yùn)行時(shí)還需要一些額外的函數(shù)。

一般來(lái)說(shuō),我們會(huì)認(rèn)真考慮運(yùn)行時(shí)原子的使用,并盡量避免不必要的原子操作。如果對(duì)變量的訪問(wèn)有時(shí)受到另一種同步機(jī)制的保護(hù),那么已經(jīng)保護(hù)的訪問(wèn)通常不需要是原子的。這有幾個(gè)原因:

  1. 在適當(dāng)?shù)牡胤绞褂梅窃釉L問(wèn)或原子訪問(wèn)使代碼更加自文檔化。對(duì)變量的原子訪問(wèn)意味著存在其他地方可以并發(fā)地訪問(wèn)該變量。
  2. 非原子訪問(wèn)允許自動(dòng)競(jìng)態(tài)檢測(cè)。運(yùn)行時(shí)目前沒(méi)有race檢測(cè)器,但將來(lái)可能會(huì)有。原子訪問(wèn)會(huì)擊敗競(jìng)爭(zhēng)檢測(cè)器,而非原子訪問(wèn)則允許競(jìng)爭(zhēng)檢測(cè)器檢查您的假設(shè)。

3.非原子訪問(wèn)可以提高性能。

當(dāng)然,任何對(duì)共享變量的非原子訪問(wèn)都應(yīng)該記錄下來(lái),以說(shuō)明如何保護(hù)這種訪問(wèn)。

混合原子訪問(wèn)和非原子訪問(wèn)的一些常見(jiàn)模式是:

  • 讀取——主要是由鎖保護(hù)更新的變量。在鎖定區(qū)域內(nèi),讀操作不需要是原子的,但寫(xiě)操作需要。在鎖定區(qū)域之外,讀操作需要是原子的。

  • 只在STW期間發(fā)生的讀操作(在STW期間不可以發(fā)生寫(xiě)操作)不需要是原子的。

也就是說(shuō),Go內(nèi)存模型的建議是:不要太聰明。運(yùn)行時(shí)的性能很重要,但它的健壯性更重要。

Unmanaged memory(非托管內(nèi)存)

通常,運(yùn)行時(shí)嘗試使用常規(guī)堆分配。然而,在某些情況下,運(yùn)行時(shí)必須在垃圾收集堆之外的*非托管內(nèi)存中分配對(duì)象。如果對(duì)象是內(nèi)存管理器本身的一部分,或者必須在調(diào)用者可能沒(méi)有P的情況下分配它們,那么這是必要的。

有三種分配非托管內(nèi)存的機(jī)制:

  • sysAlloc直接從操作系統(tǒng)獲得內(nèi)存。它的大小是系統(tǒng)頁(yè)面大小的好幾倍,但是可以通過(guò)sysFree釋放。

  • persistentalloc將多個(gè)較小的分配組合到一個(gè)sysAlloc中,以避免碎片。然而,沒(méi)有辦法釋放persistentalloced對(duì)象(因此得名)。

  • fixalloc是一個(gè)板書(shū)樣式的分配器,它分配固定大小的對(duì)象??梢葬尫舊ixalloced對(duì)象,但此內(nèi)存只能由相同的fixalloc池重用,因此它只能被相同類(lèi)型的對(duì)象重用。

通常,使用這些類(lèi)型分配的類(lèi)型應(yīng)該標(biāo)記為//go:notinheap (見(jiàn)下文)。

在非托管內(nèi)存中分配的對(duì)象必須不包含堆指針,除非也遵守以下規(guī)則:

  1. 從非托管內(nèi)存到堆的任何指針都必須是垃圾收集根。更具體地說(shuō),任何指針要么必須可以通過(guò)全局變量訪問(wèn),要么必須作為顯式的垃圾收集根添加到 runtime.markroot 中。

  2. 如果內(nèi)存被重用,堆指針在成為可見(jiàn)的GC根之前必須進(jìn)行零初始化。否則,GC可能會(huì)觀察到陳舊的堆指針。參見(jiàn)“零初始化與零化”。

Zero-initialization versus zeroing(零初始化和歸零)

在運(yùn)行時(shí)有兩種類(lèi)型的歸零,這取決于內(nèi)存是否已經(jīng)初始化為類(lèi)型安全狀態(tài)。

如果內(nèi)存不是類(lèi)型安全狀態(tài),這意味著它可能包含“垃圾”,因?yàn)樗莿倓偡峙涞?,它是初始化為第一次使用,那么它必須使?code>memclrnoheappointer或非指針寫(xiě)進(jìn)行零初始化。它不執(zhí)行寫(xiě)屏障。

如果內(nèi)存已經(jīng)處于類(lèi)型安全狀態(tài),并且只是被設(shè)置為0值,那么必須使用typedmemclrmemclrhaspointer進(jìn)行常規(guī)寫(xiě)操作。它執(zhí)行寫(xiě)屏障。

Runtime-only compiler directives(只在運(yùn)行時(shí)的編譯器指令)

除了//go:指令外,編譯器只在運(yùn)行時(shí)支持其他指令。

go:systemstack

go:systemstack 指示函數(shù)必須在系統(tǒng)堆棧上運(yùn)行。這是由一個(gè)特殊的函數(shù)序言動(dòng)態(tài)檢查的。

go:nowritebarrier

go:nowritebarrier 如果下列函數(shù)包含任何寫(xiě)障礙,則指示編譯器發(fā)出錯(cuò)誤。(它不能抑制寫(xiě)障礙的產(chǎn)生;這只是一個(gè)斷言。)

通常你會(huì)說(shuō)go:nowritebarrierrec。go:nowritebarrier在沒(méi)有寫(xiě)障礙但不要求正確性的情況下是非常有用的。

go:nowritebarrierrec and go:yeswritebarrierrec

go:nowritebarrierrec指示編譯器在下列函數(shù)或它遞歸調(diào)用的任何函數(shù)(直到go:yeswritebarrierrec)包含寫(xiě)障礙時(shí)發(fā)出錯(cuò)誤。

從邏輯上講,編譯器會(huì)從每個(gè)go:nowritebarrierrec函數(shù)開(kāi)始遍歷調(diào)用圖,如果遇到一個(gè)包含寫(xiě)障礙的函數(shù),就會(huì)產(chǎn)生錯(cuò)誤。這一批停止在 go:yeswritebarrierrec 函數(shù)。

go:nowritebarrierrec 用于實(shí)現(xiàn)寫(xiě)屏障,以防止無(wú)限循環(huán)。

這兩個(gè)指令都在調(diào)度程序中使用。寫(xiě)屏障需要一個(gè)活動(dòng)的P (` getg().m)。p ! = nil)和調(diào)度程序代碼通常沒(méi)有一個(gè)活躍的p .在這種情況下,運(yùn)行”:nowritebarrierrec”用在函數(shù)釋放p或運(yùn)行沒(méi)有和“go:yeswritebarrierrec”時(shí)使用代碼獲得積極的p .因?yàn)檫@些函數(shù)層次注釋,代碼版本或獲得p可能需要跨越兩個(gè)函數(shù)。

go:notinheap

go:notinheap適用于類(lèi)型聲明。它指出永遠(yuǎn)不能從GC d堆中分配類(lèi)型。具體地說(shuō),指向這種類(lèi)型的指針必須總是在“運(yùn)行時(shí)”失敗。inheap”檢查。該類(lèi)型可用于全局變量、堆棧變量或非托管內(nèi)存中的對(duì)象(例如,通過(guò)sysAlloc、persistentallocfixalloc分配,或從手動(dòng)管理的span)。具體地說(shuō):

  1. new(T), make([]T), append([]T, ...) and implicit heap allocation of T are disallowed. (Though implicit allocations are disallowed in the runtime anyway.)

  2. 指向普通類(lèi)型(unsafety . pointer除外)的指針不能被轉(zhuǎn)換為指向go:notinheap類(lèi)型的指針,即使它們具有相同的基礎(chǔ)類(lèi)型。

  3. 任何包含go:notinheap類(lèi)型的類(lèi)型本身就是go:notinheap。如果結(jié)構(gòu)體和數(shù)組的元素是“go:notinheap”,則它們是“go:notinheap”。地圖和通道go:notinheap類(lèi)型是不允許的。為了保持內(nèi)容的顯式性,任何類(lèi)型聲明中隱含的類(lèi)型為go:notinheap的類(lèi)型也必須顯式地標(biāo)記為go:notinheap。

  4. 在指向go:notinheap類(lèi)型的指針上的寫(xiě)障礙可以省略。

最后一點(diǎn)是go:notinheap的真正好處。運(yùn)行時(shí)將其用于低級(jí)內(nèi)部結(jié)構(gòu),以避免在調(diào)度程序和內(nèi)存分配器中存在非法或低效的內(nèi)存障礙。這種機(jī)制相當(dāng)安全,而且不會(huì)影響運(yùn)行時(shí)的可讀性。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容