萬字長文告訴你Go 1.19中值得關注的幾個變化

語言: CN / TW / HK

我們知道Go團隊在2015年重新規定了團隊釋出版本的節奏,將Go大版本的釋出頻率確定為每年兩次,釋出視窗定為每年的2月與8月。而實現自舉的 Go 1.5版本 [1] 是這一個節奏下發布的第一個版本。一般來說,Go團隊都會在這兩個視窗的中間位置釋出版本,不過這幾年也有意外,比如承載著泛型落地責任的 Go 1.18版本 [ 2] 就延遲了一個月釋出。

就在我們以為Go 1.19版本不會很快釋出的時候,美國時間2022年8月2日, Go核心團隊正式釋出了Go 1.19版本 [3] ,這個時間不僅在釋出視窗內而且相對於慣例還提前了。為什麼呢?很簡單, Go 1.19是一個“小”版本 ,當然這裡的“小”是相對於Go 1.18那樣的“大”而言的。Go 1.19版本開發週期僅有2個月左右(3~5月初),這樣Go團隊壓縮了新增到Go 1.19版本中的feature數量。

不過儘管如此,Go 1.19中依然有幾個值得我們重點關注的變化點,在這篇文章中我就和大家一起來看一下。

一. 綜述

在6月份(那時Go 1.19版本已經Freeze),我曾寫過一篇 《Go 1.19新特性前瞻》 [4] ,簡要介紹了當時基本確定的Go 1.19版本的一些新特性,現在來看,和Go 1.19版本正式版差別不大。

  • 泛型方面

考慮到Go 1.18泛型剛剛落地,Go 1.18版本中的泛型並不是完全版。但Go 1.19版本也沒有急於實現 泛型設計文件 [5] )中那些尚未實現的功能特性,而是將主要精力放在了修復Go 1.18中發現的 泛型實現問題 [6] 上了,目的是夯實Go泛型的底座,為Go 1.20以及後續版本實現完全版泛型奠定基礎(詳細內容可檢視 《Go 1.19新特性前瞻》 [7] 一文)。

  • 其他語法方面

無,無,無!重要的事情說三遍。

這樣,Go 1.19依舊保持了Go1相容性承諾。

  • 正式在linux上支援龍芯架構(GOOS=linux, GOARCH=loong64)

這一點不得不提,因為這一變化都是國內龍芯團隊貢獻的。不過目前龍芯支援的linux kernel版本最低也是5.19,意味著龍芯在老版本linux上還無法使用Go。

  • go env支援CGO_CFLAGS, CGO_CPPFLAGS, CGO_CXXFLAGS, CGO_FFLAGS, CGO_LDFLAGS和GOGCCFLAGS

當你想設定全域性的而非包級的CGO構建選項時,可以通過這些新加入的CGO相關環境變數進行,這樣就可以避免在每個使用Cgo的Go原始檔中使用cgo指示符來分別設定了。

目前這些用於CGO的go環境變數的預設值如下(以我的macos上的預設值為例):

CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
GOGCCFLAGS="-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/cz/sbj5kg2d3m3c6j650z0qfm800000gn/T/go-build1672298076=/tmp/go-build -gno-record-gcc-switches -fno-common"

其他更具體的變化就不贅述了,大家可以移步 《Go 1.19新特性前瞻》 [8] 看看。

下面我們重點說說Go 1.19中的兩個重要變化: 新版Go記憶體模型文件與Go執行時引入Soft memory limit

二. 修訂Go記憶體模型文件

記得當年初學Go的時候,所有Go官方文件中最難懂的一篇就屬 Go記憶體模型文件 [9] (如下圖)這一篇了,相信很多gopher在初看這篇文件時一定有著和我相似的趕腳^_^。

圖:老版Go記憶體模型文件

注:檢視老版Go記憶體模型文件的方法:godoc -http=:6060 -goroot /Users/tonybai/.bin/go1.18.3,其中godoc已經不隨著go安裝包分發了,需要你單獨安裝,命令為:go install golang.org/x/tools/cmd/godoc。

那麼,老版記憶體模型文件說的是啥呢?為什麼要修訂?搞清這兩個問題,我們就大致知道新版記憶體模型文件的意義了。我們先來看看什麼是程式語言的記憶體模型。

1. 什麼是記憶體模型?

提到記憶體模型,我們要從著名電腦科學家,2013年圖靈獎得主 Leslie Lamport [10] 在1979發表的名為 《How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs》 [11] 的論文說起。

在這篇文章中,Lamport給出了多處理器計算機在共享記憶體的情況下併發程式正確執行的條件,即多處理器要滿足**順序一致性(sequentially consistent)**。

文中提到:一個高速執行的處理器不一定按照程式指定的順序(程式碼順序)執行。如果一個處理器的執行結果(可能是亂序執行)與按照程式指定的順序(程式碼順序)執行的結果一致,那麼說這個處理器是**有序的(sequential)**。

而對於一個共享記憶體的多處理器而言,只有滿足下面條件,才能被認定是滿足 順序一致性 的,即具備保證併發程式正確執行的條件:

  • 任何一次執行的結果,都和所有處理器的操作按照某個順序執行的結果一致;

  • 在“某個順序執行”中單獨看每個處理器,每個處理器也都是按照程式指定的順序(程式碼順序)執行的。

順序一致性就是一個典型的共享記憶體、多處理器的記憶體模型,這個模型保證了所有的記憶體訪問都是以原子方式和按程式順序進行的。下面是一個共享記憶體的順序一致性的抽象機器模型示意圖,圖來自於 《A Tutorial Introduction to the ARM and POWER Relaxed Memory Models》 [12]

根據順序一致性,上面圖中的抽象機器具有下面特點:

  • 沒有本地的重新排序:每個硬體執行緒按照程式指定的順序執行指令,完成每條指令(包括對共享記憶體的任何讀或寫)後再開始下一條。

  • 每條寫入指令對所有執行緒(包括進行寫入的執行緒)都是同時可見的。

從程式設計師角度來看,順序一致性的記憶體模型是再理想不過了。所有讀寫操作直面記憶體,沒有快取,一個處理器(或硬體執行緒)寫入記憶體的值,其他處理器(或硬體執行緒)便可以觀察到。藉助硬體提供的順序一致性(SC),我們可以實現“所寫即所得”。

但是這樣的機器真的存在嗎?並沒有,至少在量產的機器中並沒有。為什麼呢?因為順序一致性不利於硬體和軟體的效能優化。真實世界的共享記憶體的多處理器計算機的常見機器模型是這樣的,也稱為Total Store Ordering,TSO模型(圖來自 《A Tutorial Introduction to the ARM and POWER Relaxed Memory Models》 [13] ):

我們看到,在這種機器下,所有處理器仍連線到單個共享記憶體,但每個處理器的寫記憶體操作從寫入共享記憶體變為了先寫入本處理器的寫快取佇列(write buffer),這樣處理器無需因要等待寫完成(write complete)而被阻塞,並且一個處理器上的讀記憶體操作也會先查閱本處理器的寫快取佇列(但不會查詢其他處理器的寫快取佇列)。寫快取佇列的存在極大提升了處理器寫記憶體操作的速度。

但也正是由於寫快取的存在,TSO模型無法滿足順序一致性,比如:“每條寫入指令對所有執行緒(包括進行寫入的執行緒)都是同時可見的”這一特性就無法滿足,因為寫入本地寫快取佇列的資料在未真正寫入共享記憶體前只對自己可見,對其他處理器(硬體執行緒)並不可見。

根據Lamport的理論,在不滿足SC的多處理器機器上程式設計師沒法開發出可以正確執行的併發程式(Data Race Free, DRF),那麼怎麼辦呢?處理器提供同步指令給開發者。對開發者而言,有了同步指令的非SC機器,具備了SC機器的屬性。只是這一切對開發人員不是自動的/透明的了,需要開發人員熟悉同步指令,並在適當場合,比如涉及資料競爭Data Race的場景下正確使用,這大大增加了開發人員的心智負擔。

開發人員通常不會直面硬體,這時就要求高階程式語言對硬體提供的同步指令進行封裝並提供給開發人員,這就是 程式語言的同步原語 。而程式語言使用哪種硬體同步指令,封裝出何種行為的同步原語,怎麼應用這些原語,錯誤的應用示例等都是需要向程式語言的使用者進行說明的。而這些都將是程式語言記憶體模型文件的一部分。

如今主流的程式語言的記憶體模型都是 順序一致性(SC)模型 ,它為開發人員提供了一種理想的SC機器(雖然實際中的機器並非SC的),程式是建構在這一模型之上的。但就像前面說的,開發人員要想實現出正確的併發程式,還必須瞭解程式語言封裝後的同步原語以及他們的語義。 只要程式設計師遵循併發程式的同步要求合理使用這些同步原語,那麼編寫出來的併發程式就能在非SC機器上跑出順序一致性的效果

知道了程式語言記憶體模型的含義後,接下來,我們再來看看老版Go記憶體模型文件究竟表述了什麼。

2. Go記憶體模型文件

按照上面的說明,Go記憶體模型文件描述的應該是 要用Go寫出一個正確的併發程式所要具備的條件

再具體點,就像老版記憶體模型文件開篇所說的那樣: Go記憶體模型規定了一些條件,一旦滿足這些條件,當在一個goroutine中讀取一個變數時,Go可以保證它可以觀察到不同goroutine中對同一變數的寫入所產生的新值

接下來,記憶體模型文件就基於常規的happens-before定義給出了Go提供的各種同步操作及其語義,包括:

  • 如果一個包p匯入了包q,那麼q的init函式的完成發生在p的任何函式的開始之前。

  • 函式main.main的開始發生在所有init函式完成之後。

  • 啟動一個新的goroutine的go語句發生在goroutine的執行開始之前。

  • 一個channel上的傳送操作發生在該channel的對應接收操作完成之前。

  • 一個channel的關閉發生在一個返回零值的接收之前(因為該channel已經關閉)。

  • 一個無緩衝的channel的接收發生在該channel的傳送操作完成之前。

  • 一個容量為C的channel上的第k個接收操作發生在該channel第k+C個傳送操作完成之前。

  • 對於任何sync.Mutex或sync.RWMutex變數l,當n<m時,第n次l.Unlock呼叫發生在第m次呼叫l.Lock()返回之前。

  • once.Do(f)中的f()呼叫發生在對once.Do(f)的任何一次呼叫返回之前。

接下來,記憶體模型文件還定義了一些誤用同步原語的例子。

那麼新記憶體模型文件究竟更新了哪些內容呢?我們繼續往下看。

3. 修訂後的記憶體模型文件都有哪些變化

圖:修訂後的Go記憶體模型文件

負責更新記憶體模型文件的Russ Cox首先增加了**Go記憶體模型的總體方法(overall approach)**。

Go的總體方法在C/C++和Java/Js之間,既不像C/C++那樣將存在Data race的程式定義為違法的,讓編譯器以未定義行為處置它,即執行時表現出任意可能的行為;又不完全像Java/Js那樣儘量明確Data Race情況下各種語義,將Data race帶來的影響限制在最小,使程式更為可靠。

Go對於一些存在data Race的情況會輸出race報告並終止程式,比如多goroutine在未使用同步手段下對map的併發讀寫。除此之外,Go對其他存資料競爭的場景有明確的語義,這讓程式更可靠,也更容易除錯。

其次,新版Go記憶體模型文件增補了對這些年sync包新增的API的說明,比如:mutex.TryLock、mutex.TryRLock等。而對於sync.Cond、Map、Pool、WaitGroup等文件沒有逐一描述,而是建議看API文件。

在老版記憶體模型文件中,沒有對sync/atom包進行說明,新版文件增加了對atom包以及runtime.SetFinalizer的說明。

最後,文件除了提供不正確同步的例子,還增加了對不正確編譯的例子的說明。

另外這裡順便提一下:Go 1.19在atomic包中引入了一些新的原子型別,包括:Bool, Int32, Int64, Uint32, Uint64, Uintptr和Pointer。這些新型別讓開發人員在使用atomic包是更為方便,比如下面是Go 1.18和Go 1.19使用Uint64型別原子變數的程式碼對比:

對比Uint64的兩種作法:

// Go 1.18

var i uint64
atomic.AddUint64(&i, 1)
_ = atomic.LoadUint64(&i)

vs.

// Go 1.19
var i atomic.Uint64 // 預設值為0
i.Store(17) // 也可以通過Store設定初始值
i.Add(1)
_ = i.Load()

atomic包新增的Pointer,避免了開發人員在使用原子指標時自己使用unsafe.Pointer進行轉型的麻煩。同時atomic.Pointer是一個泛型型別,如果我沒記錯,它是Go 1.18加入comparable預定義泛型型別之後,第一次在Go中引入基於泛型的標準庫型別:

// $GOROOT/src/sync/atomic/type.go

// A Pointer is an atomic pointer of type *T. The zero value is a nil *T.
type Pointer[T any] struct {
_ noCopy
v unsafe.Pointer
}

// Load atomically loads and returns the value stored in x.
func (x *Pointer[T]) Load() *T { return (*T)(LoadPointer(&x.v)) }

// Store atomically stores val into x.
func (x *Pointer[T]) Store(val *T) { StorePointer(&x.v, unsafe.Pointer(val)) }

// Swap atomically stores new into x and returns the previous value.
func (x *Pointer[T]) Swap(new *T) (old *T) { return (*T)(SwapPointer(&x.v, unsafe.Pointer(new))) }

// CompareAndSwap executes the compare-and-swap operation for x.
func (x *Pointer[T]) CompareAndSwap(old, new *T) (swapped bool) {
return CompareAndSwapPointer(&x.v, unsafe.Pointer(old), unsafe.Pointer(new))
}

此外,atomic包新增的Int64和Uint64型別還有一個特質,那就是Go保證其地址可以自動對齊到8位元組上(即地址可以被64整除),即便在32位平臺上亦是如此,這可是 連原生int64和uint64也尚無法做到的 [14]

go101 [15] 在推特上分享了一個基於atomic Int64和Uint64的tip。利用go 1.19新增的atomic.Int64/Uint64,我們可以用下面方法保證結構體中某個欄位一定是8 byte對齊的,即該欄位的地址可以被64整除。

import "sync/atomic"

type T struct {
_ [0]atomic.Int64
x uint64 // 保證x是8位元組對齊的
}

前面的程式碼中,為何不用_ atomic.Int64呢,為何用一個空陣列呢,這是因為空陣列在go中不佔空間,大家可以試試輸出上面結構體T的size,看看是不是8。

三. 引入Soft memory limit

1. 唯一GC調優選項:GOGC

近幾個大版本,Go GC並沒有什麼大的改動/優化。和其他帶GC的程式語言相比,Go GC算是一個奇葩的存在了:對於開發者而言,Go 1.19版本之前,Go GC的調優引數僅有一個: GOGC (也可以通過runtime/debug.SetGCPercent調整)。

GOGC預設值為100,通過調整它的值,我們可以調整GC觸發的時機。計算下一次觸發GC的堆記憶體size的公式如下:

// Go 1.18版本之前
目標堆大小 = (1+GOGC/100) * live heap // live heap為上一次GC標記後的堆上的live object的總size

// Go 1.18版本及之後
目標堆大小 = live heap + (live heap + GC roots) * GOGC / 100

注:Go 1.18以後將GC roots(包括goroutine棧大小和全域性變數中的指標物件大小)納入目標堆大小的計算

以Go 1.18之前的版本為例,當GOGC=100(預設值)時,如果某一次GC後的live heap為10M,那麼下一次GC開啟的目標堆heap size為20M,即在兩次GC之間,應用程式可以分配10M的新堆物件。

可以說 GOGC控制著GC的執行頻率 。當GOGC值設定的較小時,GC執行的就頻繁一些,參與GC工作的cpu的比重就多一些;當GOGC的值設定的較大時,GC執行的就不那麼頻繁,相應的參與GC工作的cpu的比重就小一些,但要承擔記憶體分配接近資源上限的風險。

這樣一來,擺在開發者面前的問題就是:GOGC的值很難選,這唯一的調優選項也就成為了擺設。

同時,Go runtime是不關心資源limit的,只是會按照應用的需求持續分配記憶體,並在自身記憶體池不足的情況下向OS申請新的記憶體資源,直到記憶體耗盡(或到達平臺給應用分配的memory limit)而被oom killed!

為什麼有了GC,Go應用還是會因耗盡系統memory資源而被oom killed呢?我們繼續往下看。

2. Pacer的問題

上面的觸發GC的目標堆大小計算公式,在Go runtime內部被稱為pacer演算法,pacer中文有翻譯成“起搏器”的,有譯成“配速器”的。不管譯成啥,總而言之它是用來 控制GC觸發節奏的

不過pacer目前的演算法是無法保證你的應用不被OOM killed的,舉個例子(見下圖):

在這個例子中:

  • 一開始live heap始終平穩,淨增的heap object保持0,即新分配的heap object與被清掃掉的heap object相互抵消。

  • 後續在(1)處出現一次target heap的躍升(從h/2->h),原因顯然是live heap object變多了,都在用,即便觸發GC也無法清除。不過此時target heap(h)是小於hard memory limit的;

  • 程式繼續執行,在(2)處,又出現一次target heap的躍升(從h->2h),而live heap object也變多了,穩定在h,此時,target heap變為2h,高於hard memory limit了;

  • 後續程式繼續執行,當live heap object到達(3)時,實際Go的堆記憶體(包括未清理的)超過了hard memory limit,但由於尚未到達target heap(2h),GC沒有被執行,因此應用被oom killed。

我們看到這個例子中,並非Go應用真正需要那麼多記憶體(如果有GC及時清理,live heap object就在(3)的高度), 而是Pacer演算法導致了沒能及時觸發GC

那麼如何儘可能的避免oom killed呢?我們接下來看一下Go社群給出了兩個“民間偏方”。

3. Go社群的GC調優方案

這兩個“偏方”, 一個是 twitch遊戲公司給出的memory ballast(記憶體壓艙石) [16] ,另外一個則是 像uber這樣的大廠採用的自動GC動態調優方案 [17] 。當然這兩個方案不光是要避免oom,更是為了優化GC,提高程式的執行效率。

下面我們分別簡單介紹一下。先來說說twitch公司的memory ballast。twitch的Go服務執行在具有64G實體記憶體的VM上,通過觀察運維人員發現,服務常駐的實體記憶體消耗僅為400多M,但Go GC的啟動卻十分頻繁,這導致其服務響應的時間較長。twitch的工程師考慮充分利用記憶體,降低GC的啟動頻率,從而降低服務的響應延遲。

於是他們想到了一種方法,他們在服務的main函式初始化環節像下面這樣聲明瞭一個10G容量的大切片,並保證這個切片在程式退出前不被GC釋放掉:

func main() {
// Create a large heap allocation of 10 GiB
ballast := make([]byte, 10<<30)

// Application execution continues
// ...

runtime.Keepalive(ballast)
// ... ...
}

這個切片由於太大,將在堆上分配並被runtime跟蹤,但這個切片並不會給應用帶去實質上的實體記憶體消耗,這得益於os對應用程序記憶體的 延遲簿記 :只有讀寫的記憶體才會導致缺頁中斷並由OS為之分配實體記憶體。從類似top的工具來看,這10個G的位元組僅會記錄在VIRT/VSZ(虛擬記憶體)上,而不會記錄在RES/RSS(常駐記憶體)上。

這樣一來,根據前面Pacer演算法的原理,觸發GC的下一個目標堆大小就至少為20G,在Go服務分配堆記憶體到20G之前GC都不會被觸發,所有cpu資源都會被用來處理業務,這也與twitch的實測結果一致(GC次數下降99%)。

一旦到了20G,由於之前觀測的結果是服務僅需400多M實體記憶體,大量heap object會被回收,Go服務的live heap會回到400多M,但重新計算目標堆記憶體時,由於前面那個“壓艙石”的存在,目標堆記憶體已經會在至少20G的水位上,就這樣GC次數少了,GC少了,worker goroutine參加“勞役”的時間就少了,cpu利用率高了,服務響應的延遲也下來了。

注:“勞役”是指worker goroutine在mallocgc記憶體時被runtime強制“勞役”:停下自己手頭的工作,去輔助GC做heap live object的mark。

不過使用該方案的前提是你對你的Go服務的記憶體消耗情況(忙閒時)有著精確的瞭解,這樣才能結合硬體資源情況設定合理的ballast值。

按照 Soft memory limit proposal [18] 的說法,該方案的弊端如下:

  • 不能跨平臺移植,據說Windows上不適用(壓艙石的值會直接反映為應用的實體記憶體佔用);

  • 不能保證隨著Go執行時的演進而繼續正常工作(比如:一旦pacer演算法發生了巨大變化);

  • 開發者需要進行復雜的計算並估計執行時記憶體開銷以選擇適合的ballast大小。

接下來我們再來看看自動GC動態調優方案。

去年12月,uber在其官方部落格分享了uber內部使用的 半自動化Go GC調優方案 [19] ,按uber的說法,這種方案實施後幫助uber節省了70K cpu核的算力。其背後的原理依舊是從Pacer的演算法公式出發,改變原先Go服務生命週期全程保持GOGC值靜態不變的作法,在每次GC時,依據容器的記憶體限制以及當前的live heap size動態計算並設定GOGC值,從而實現對記憶體不足oom-killed的保護,同時最大程度利用記憶體,改善Gc對cpu的佔用率。

顯然這種方案更為複雜,需要有一個專家團隊來保證這種自動調優的引數的設定與方案的實現。

4. 引入Soft memory limit

其實Go GC pacer的問題還有很多, Go核心團隊開發者Michael Knyszek提了一個 pacer問題綜述的issue [20] ,將這些問題做了彙總。但問題還需一個一個解決,在Go 1.19這個版本中,Michael Knyszek就帶來了他的 Soft memory limit的解決方案 [21]

這個方案在runtime/debug包中添加了一個名為SetMemoryLimit的函式以及GOMEMLIMIT環境變數,通過他們任意一個都可以設定Go應用的Memory limit。

一旦設定了Memory limit,當Go堆大小達到“Memory limit減去非堆記憶體後的值”時,一輪GC會被觸發。即便你手動關閉了GC(GOGC=off),GC亦是會被觸發。

通過原理我們可以看到,這個特性最直接解決的就是oom-killed這個問題!就像前面pacer問題示意圖中的那個例子,如果我們設定了一個比hard memory limit小一些的soft memory limit的值,那麼在(3)那個點便不會出現oom-killed,因為在那之前soft memory limit就會觸發一次GC,將一些無用的堆記憶體回收掉了。

但我們也要注意:soft memory limit不保證不會出現oom-killed,這個也很好理解。如果live heap object到達limit了,說明你的應用記憶體資源真的不夠了,是時候擴記憶體條資源了,這個是GC無論如何都無法解決的問題。

但如果一個Go應用的live heap object超過了soft memory limit但還尚未被kill,那麼此時GC會被持續觸發,但為了保證在這種情況下業務依然能繼續進行,soft memory limit方案保證GC最多隻會使用50%的CPU算力,以保證業務處理依然能夠得到cpu資源。

對於GC觸發頻率高,要降低GC頻率的情況,soft memory limit的方案就是**關閉GC(GOGC=off)**,這樣GC只有當堆記憶體到達soft memory limit值時才會觸發,可以提升cpu利用率。不過有一種情況, Go官方的GC guide [22] 中不建議你這麼做,那就是當你的Go程式與其他程式共享一些有限的記憶體時。這時只需保留記憶體限制並將其設定為一個較小的合理值即可,因為它可能有助於抑制不良的瞬時行為。

那麼多大的值是合理的soft memory limit值呢?在Go服務獨佔容器資源時,一個好的經驗法則是留下額外的5-10%的空間,以考慮Go執行時不知道的記憶體來源。uber在其部落格中設定的limit為資源上限的70%,也是一個不錯的經驗值。

四. 小結

也許Go 1.19因開發週期的壓縮給大家帶來的驚喜並不多。不過特性雖少,卻都很實用,比如上面的soft memory limit,一旦用好,便可以幫助大家解決大問題。

而擁有正常開發週期的Go 1.20已經處於積極的開發中,從目前 里程碑 [23] 中規劃的功能和改進來看,Go泛型語法將得到進一步的補全,向著完整版邁進,就這一點就值得大家期待了!

五. 參考資料

  • Russ Cox記憶體模型系列 - https://research.swtch.com/mm

  • 關於Go記憶體模型的討論 - https://github.com/golang/go/discussions/47141

  • How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs- https://www.microsoft.com/en-us/research/publication/make-multiprocessor-computer-correctly-executes-multiprocess-programs

  • A Tutorial Introduction to the ARM and POWER Relaxed Memory Models- https://www.cl.cam.ac.uk/~pes20/ppc-supplemental/test7.pdf

  • Weak Ordering - A New Definition- https://people.eecs.berkeley.edu/~kubitron/courses/cs258-S08/handouts/papers/adve-isca90.pdf

  • Foundations of the C++ Concurrency Memory Model - https://www.hpl.hp.com/techreports/2008/HPL-2008-56.pdf

  • Go GC pacer原理 - https://docs.google.com/document/d/1wmjrocXIWTr1JxU-3EQBI6BK6KgtiFArkG47XK73xIQ/edit

參考資料

[1] 

Go 1.5版本: https://tonybai.com/2015/07/10/some-changes-in-go-1-5/

[2] 

Go 1.18版本: https://tonybai.com/2022/04/20/some-changes-in-go-1-18

[3] 

Go核心團隊正式釋出了Go 1.19版本: https://go.dev/blog/go1.19

[4] 

《Go 1.19新特性前瞻》: https://tonybai.com/2022/06/12/go-1-19-foresight

[5] 

泛型設計文件: https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md

[6] 

泛型實現問題: https://github.com/golang/go/issues?q=is%3Aissue+label%3Agenerics+milestone%3AGo1.19

[7] 

《Go 1.19新特性前瞻》: https://tonybai.com/2022/06/12/go-1-19-foresight

[8] 

《Go 1.19新特性前瞻》: https://tonybai.com/2022/06/12/go-1-19-foresight

[9] 

Go記憶體模型文件: https://go.dev/ref/mem

[10] 

Leslie Lamport: https://www.microsoft.com/en-us/research/people/lamport/

[11] 

《How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs》: https://www.microsoft.com/en-us/research/publication/make-multiprocessor-computer-correctly-executes-multiprocess-programs/

[12] 

《A Tutorial Introduction to the ARM and POWER Relaxed Memory Models》: https://www.cl.cam.ac.uk/~pes20/ppc-supplemental/test7.pdf

[13] 

《A Tutorial Introduction to the ARM and POWER Relaxed Memory Models》: https://www.cl.cam.ac.uk/~pes20/ppc-supplemental/test7.pdf

[14] 

連原生int64和uint64也尚無法做到的: https://github.com/golang/go/issues/36606

[15] 

go101: https://go101.org/

[16] 

twitch遊戲公司給出的memory ballast(記憶體壓艙石): https://es.blog.twitch.tv/tr-tr/2019/04/10/go-memory-ballast-how-i-learnt-to-stop-worrying-and-love-the-heap/

[17] 

像uber這樣的大廠採用的自動GC動態調優方案: https://www.uber.com/en-US/blog/how-we-saved-70k-cores-across-30-mission-critical-services/

[18] 

Soft memory limit proposal: https://github.com/golang/proposal/blob/master/design/48409-soft-memory-limit.md

[19] 

半自動化Go GC調優方案: https://www.uber.com/en-US/blog/how-we-saved-70k-cores-across-30-mission-critical-services/

[20] 

pacer問題綜述的issue: https://github.com/golang/go/issues/42430

[21] 

Soft memory limit的解決方案: https://github.com/golang/proposal/blob/master/design/48409-soft-memory-limit.md

[22] 

Go官方的GC guide: https://go.dev/doc/gc-guide

[23] 

里程碑: https://github.com/golang/go/milestone/250

推薦閱讀

福利

我為大家整理了一份 從入門到進階的Go學習資料禮包 ,包含學習建議:入門看什麼,進階看什麼。 關注公眾號 「polarisxu」,回覆  ebook  獲取;還可以回覆「 進群 」,和數萬 Gopher 交流學習。