Go 1.14 正式發佈,重要更新有哪些

Go 在 2019 年發佈了Go 1.12與Go 1.13。Go 1.13 的大部分變化在於工具鏈、運行時和庫的實現。時隔半年,Go 1.14 正式發佈。

和之前的版本一樣,該版本保留了 Go 1 兼容性的承諾,這個版本的大部分更新在工具鏈 、運行時庫的性能提升方面。總的來說,還是在已有的基礎上不斷優化提成,大家期待的泛型還沒有到來,下面一塊看看新的變化吧。重大的更新如下:

  1. Go 命令中的 Module 支持現在可以投入生產

  2. 嵌入具有重疊方法集的接口

  3. defer 性能改進

  4. goroutine 支持異步搶佔

  5. 工具的變化

  6. time.Timer 定時器性能大幅提升

Go 命令中的 Module 支持現在可以投入生產

現在,可以在 Go 命令中使用 Module 支持,以供生產使用,並且鼓勵所有用戶遷移到 Go Module 以進行依賴項管理。

嵌入具有重疊方法集的接口

Go 1.14 現在允許嵌入具有重疊方法集的接口:來自嵌入式接口的方法允許與 (嵌入) 接口中已存在的方法擁有相同的名稱和簽名。

在 Go 1.14 之前,如下的定義會編譯報錯。

type ReadWriteCloser interface {
    io.ReadCloser
    io.WriteCloser
}


因爲 io.ReadCloser 和 io.WriteCloser 中 Close 方法重複了。Go 1.14開始允許相同簽名的方法可以內嵌入一個接口中。與以前一樣,接口中顯式聲明的方法必須保持唯一性。

defer 性能改進

Go1.14 提高了 defer 的大多數用法的性能,幾乎 0 開銷!defer 已經可以用於對性能要求很高的場景了。

關於 defer,在Go 1.13 版本已經做了一些的優化,相較於 Go 1.12,defer 大多數用法性能提升了 30%。而 Go 1.14 的此次改進之後更加高效!

goroutine 支持異步搶佔

調度器使用的 G-M-P 模型。下面是相關的概念:

  • G(Goroutine):goroutine,由關鍵字 go 創建

  • M(Machine):在 Go 中稱爲 Machine,可以理解爲工作線程

  • P(Processor):處理器 P 是線程 M 和 Goroutine 之間的中間層(並不是CPU)

M 必須持有 P 才能執行 G 中的代碼,P有自己本地的一個運行隊列,由可運行的 G 組成,Go 語言調度器的工作原理就是處理器P的隊列中選擇隊列頭的 goroutine 放到線程 M 上執行,上圖展示了 線程 M、處理器 P 和 goroutine 的關係。

每個P維護的G可能是不均衡的,調度器還維護了一個全局G隊列,當P執行完本地的G任務後,會嘗試從全局隊列中獲取G任務運行(需要加鎖),當P本地隊列和全局隊列都沒有可運行的任務時,會嘗試偷取其他P中的G到本地隊列運行(任務竊取)。

在 Go 1.1 版本中,調度器還不支持搶佔式調度,只能依靠 goroutine 主動讓出 CPU 資源,存在非常嚴重的調度問題:

  • 單獨的 goroutine 可以一直佔用線程運行,不會切換到其他的 goroutine,造成飢餓問題

  • 垃圾回收需要暫停整個程序(Stop-the-world,STW),如果沒有搶佔可能需要等待幾分鐘的時間,導致整個程序無法工作

Go 1.12 中編譯器在特定時機插入函數,通過函數調用作爲入口觸發搶佔,實現了協作式的搶佔式調度。但是這種需要函數調用主動配合的調度方式存在一些邊緣情況,就比如說下面的例子:

import (
    "runtime"
    "time"
)

func main() {
    runtime.GOMAXPROCS(1)
    go func() { //創建一個goroutine並掛起
        for {
        }
    }()
    time.Sleep(time.Millisecond) //main goroutine 優先調用了 休眠
    println("OK")
}

此時唯一的 P 會轉去執行 for 循環所創建的 goroutine,進而 main goroutine 永遠不會再被調度。換一句話說在Go1.14之前,上邊的代碼永遠不會輸出 OK。這是因爲 Go 1.12 實現的協作式的搶佔式調度是不會使一個沒有主動放棄執行權、且不參與任何函數調用的 goroutine 被搶佔。

Go1.14 通過實現了基於信號的真搶佔式調度解決了上述問題,這是一個非常大的改動,Go 團隊對已有的邏輯進行重構併爲 goroutine 增加新的狀態和字段來支持搶佔。沒有函數調用的循環不再能致使調度程序死鎖或影響 GC。除了 Windows/arm,darwin/arm,js/wasm 和 plan9/* 之外的所有平臺均支持此功能。

實施搶佔的結果是,在包括 Linux 和 macOS 系統在內的 Unix 系統上,使用 Go 1.14 構建的程序將比使用早期版本構建的程序接收更多的信號。這意味着使用諸如 syscall 或 golang.org/x/sys/unix 之類的軟件包的程序將看到更多較慢的系統調用,並出現 EINTR 錯誤。這些程序將必須以某種方式處理那些錯誤,最有可能的循環是再次嘗試系統調用。有關此內容的更多信息,請參見用於 Linux 系統的 man 7 signal 或用於其他系統的類似文檔。

工具的變化

關於Go1.14中對工具的完善,主要說一下 go mod 和 go test,Go官方肯定希望開發者使用官方的包管理工具,Go1.14 完善了很多功能。
go mod 主要做了以下改進:

  • incompatiable versions:如果模塊的最新版本包含go.mod文件,則除非明確要求或已經要求該版本,否則go get將不再升級到該模塊的不兼容主要版本。直接從版本控制中獲取時,go list還會忽略此模塊的不兼容版本,但如果由代理報告,則可能包括這些版本。

  • go.mod文件維護:除了 go mod tidy 之外的 go 命令不再刪除 require指令,該指令指定了間接依賴版本,該版本已由主模塊的其他依賴項隱含。除了 go mod tidy 之外的 go 命令不再編輯 go.mod 文件,如果更改只是修飾性的。

  • Module下載:在module模式下,go命令支持 SVN 倉庫,go 命令現在包括來自模塊代理和其他HTTP服務器的純文本錯誤消息的摘要。如果錯誤消息是有效的UTF-8,且包含圖形字符和空格,只會顯示錯誤消息。

go test -v 現在將 t.Log 輸出流式傳輸,而不是在所有測試數據結束時輸出。

time.Timer 定時器性能大幅提升

在 Go 1.10 之前的版本中,Go語言使用1個全局的四叉小頂堆維護所有的timer。由time.after,time.Tick,net.Conn.SetDeadline和friends所使用的內部計時器效率更高,鎖爭用更少,上下文切換更少。這是一項性能改進,不會引起任何用戶可見的更改。

這邊具體的改進,大家可以自行了解下,相對比較複雜,筆者正在學習最新的實現,後續專門講這部分內容。

小結

Go 1.14 還有很多其他變更:

  • WebAssembly的變化

  • reflect包的變化

  • 很多其他重要的包(math,http等)的改變

Go語言的錯誤處理提案獲得了社區很多人的支持,但是也有很多人反對,結論是:Go已經放棄了這一提案!這些思想還沒有得到充分的發展,尤其考慮到更改語言的實現成本時,所以有關枚舉和不可變類型,Go語言團隊最近也是不給予考慮實現的。

Go1.14 也有一些計劃中但是未完成的工作,Go1.14 嘗試優化頁分配器(page allocator),能夠實現在 GOMAXPROCS 值比較大時,顯著減少鎖競爭。這一改動影響很大,能顯著的提高 Go 並行能力,也會進一步提升 timer 的性能。但是由於實現起來比較複雜,有一些來不及解決的問題,要 delay 到 Go1.15 完成了。

關於 Go 1.14 的詳細發佈日誌,可參見 https://golang.org/doc/go1.14。

參考

  1. go 1.14 https://golang.org/doc/go1.14

  2. 關於Go1.14,你一定想知道的性能提升與新特性 https://studygolang.com/articles/26529?fr=sidebar

訂閱最新文章,歡迎關注我的公衆號

微信公衆號
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章