golang1.21新特性速覽

来源:https://www.cnblogs.com/apocelipes/archive/2023/08/09/17617494.html
-Advertisement-
Play Games

經過了半年左右的開發,golang 1.21 在今天早上正式發佈了。 這個版本中有不少重要的新特性和變更,尤其是在泛型相關的代碼上。 因為有不少大變動,所以建議等第一個patch版本也就是1.21.1出來之後再進行升級,以免遇到一些意外的bug帶來麻煩。 好了,一起來看看1.21帶來的新特性吧。 本 ...


經過了半年左右的開發,golang 1.21 在今天早上正式發佈了。

這個版本中有不少重要的新特性和變更,尤其是在泛型相關的代碼上。

因為有不少大變動,所以建議等第一個patch版本也就是1.21.1出來之後再進行升級,以免遇到一些意外的bug帶來麻煩。

好了,一起來看看1.21帶來的新特性吧。

本文索引

新的內置函數

1.21添加了三個新的內置函數:minmaxclear

minmax如其字面意思,用了選出一組變數里(數量大於等於1,只有一個變數的時候就返回那個變數的值)最大的或者最小的值。兩個函數定義是這樣的:

func min[T cmp.Ordered](x T, y ...T) T
func max[T cmp.Ordered](x T, y ...T) T

註意那個類型約束,這是新的標準庫里提供的,原型如下:

type Ordered interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
    ~float32 | ~float64 |
    ~string
}

也就是說只有基於所有除了map,chan,slice以及複數之外的基本類型的變數才能使用這兩個函數。或者換句話說,只有可以使用<><=>===!=進行比較的類型才可以使用min和max。

有了min和max,可以把許多自己手寫的代碼替換成新的內置函數,可以少寫一些幫助函數。而且使用新的內置函數還有一個好處,在變數個數比較少的時候還有編譯器的優化可用,比自己寫min函數性能上要稍好一些。

使用上也很簡單:

numbers := []int{7, 6, 5, 4, 3, 2, 1}
maxIntValue := max(0, numbers...) // 7 type int
minIntValue := min(8, numbers...) // 1 type int

對於clear來說事情比min和max複雜。clear只接受slice和map,如果是對泛型的類型參數使用clear,那麼類型參數的type set必須是map或者slice,否則編譯報錯。

clear的定義如下:

func clear[T ~[]Type | ~map[Type]Type1](t T)

對於參數是map的情況,clear會刪除所有map里的元素(不過由於golang的map本身的特性,map存儲的數據會被正常銷毀,但map已經分配的空間不會釋放):

func main() {
        m := map[int]int{1:1, 2:2, 3:3, 4:4, 5:5}
        fmt.Println(len(m))  // 5
        clear(m)
        fmt.Println(len(m))  // 0
}

燃而對於slice,它的行為又不同了:會把slice里所有元素變回零值。看個例子:

func main() {
        s := make([]int, 0, 100) // 故意給個大的cap便於觀察
        s = append(s, []int{1, 2, 3, 4, 5}...)
        fmt.Println(s) // [1 2 3 4 5]
        fmt.Println(len(s), cap(s)) // len: 5; cap: 100
        clear(s)
        fmt.Println(s) // [0 0 0 0 0]
        fmt.Println(len(s), cap(s)) // len: 5; cap: 100
}

這個就比較反直覺了,畢竟clear首先想到的應該是把所有元素刪除。那它的意義是什麼呢?對於map來說意義是很明確的,但對於slice來說就有點繞彎了:

slice的真實大小是cap所顯示的那個大小,如果只是用s := s[:0]來把所有元素“刪除”,那麼這些元素實際上還是留在記憶體里的,直到s本身被gc回收或者往s里添加新元素把之前的對象覆蓋掉,否則這些對象是不會被回收掉的,這一方面可以提高記憶體的利用率,另一方面也會帶來泄露的問題(比如存儲的是指針類型或者包含指針類型的值的時候,因為指針還存在,所以被指向的對象始終有一個有效的引用導致無法被回收),所以golang選擇了折中的辦法:把所有已經存在的元素設置成0值

如果想安全的正常刪除slice的所有元素,有想復用slice的記憶體,該怎麼辦?答案是:

s := make([]T, 0, 100) // 故意給個大的cap便於觀察
s = append(s, []T{*new(T), *new(T)}...)

clear(s)
s = s[:0]

如果沒有內置函數clear,那麼我們得自己迴圈一個個賦值處理。而有clear的好處是,編譯器會直接用memset把slice的記憶體里的數據設置為0,比迴圈會快很多。有興趣的可以看看clear在slice上的實現:代碼在這

類型推導更加智能

其實就是bug修複,以前類似這樣的代碼在某些情況下沒法正常進行推導:

func F[T ~E[], E any](t T, callable func(E))

func generic[E any](e E) {}
F(t, generic) // before go1.21: error; after go1.21: ok

理論上只要能推導出E的類型,那麼Fgeneric的所有類型參數都能推導出來,哪怕generic本身是個泛型函數。以前想正常使用就得這麼寫:F(t, generic[Type])

所以與其說是新特性,不如說是對類型推導的bug修複。

針對類型推導還有其他一些修複和報錯信息的內容優化,但這些都沒上面這個變化大,所以恕不贅述。

panic的行為變化

1.21開始如果goroutine里有panic,那麼這個goroutine里的defer里調用的recover必然不會返回nil值。

這導致了一個問題:recover的返回值是傳給panic的參數的值,panic(nil)這樣的代碼怎麼辦?

先要提醒一下,panic(nil)本身是無意義的,且會導致recover的調用方無法判斷究竟發生了什麼,所以一直是被各類linter包括go vet命令警告的。然而這麼寫語法上完全正確,所以只有警告並不能解決問題。

解決辦法是,如果現在使用panic(nil)或者panic(值為nil的介面),recover會收到一個新類型的error:*runtime.PanicNilError

總體上算是解決了問題,然而它把有類型的但值是nil的介面也給轉換了,雖然從最佳實踐的角度來講panic一個空值的介面是不應該的,但多少還是會給使用上帶來一些麻煩。

所以目前想要禁用這一行為的話,可以設置環境變數:export GODEBUG=panicnil=1。如果go.mod里聲明的go版本小於等於1.20,這個環境變數的功能自動啟用。

對於modules的變化,會在下一節講解。

modules的變化

最大的變化是現在mod文件里寫的go版本號的意義改變了。

變成了:mod文件里寫的go的版本意味著這個mod最低支持的golang版本是多少。

比如:

module github.com/apocelipes/flatmap

go 1.21.0

意味著這個modules最低要求go的版本是go1.21.0,而且可以註意到,現在patch版本也算在內里,所以一個聲明為go 1.21.1的modules沒法被1.21.0版本的go編譯。

這麼做的好處是能嚴格控制自己的程式和庫可以在哪些版本的golang上運行,且可以推動庫版本和golang本身版本的升級。

如果嚴格按照官方要求使用語義版本來控制自己的modules的話,這個改動沒有什麼明顯的壞處,唯一的缺點是只有1.21及更高版本的go工具鏈才有這樣的功能。

這個變更對go.work文件同樣適用。

包初始化順序的改變

現在按新的順序來初始化包:

  1. 把所有的packages按導入路徑進行排序(字元串字典順序)存進一個列表
  2. 按要求和順序找到列表裡第一個符合條件的package,要求是這個package所有的import的包都已經完成初始化
  3. 初始化這個找到的包然後把它移出列表,接著重覆第二步
  4. 列表為空的時候初始化流程結束

這樣做的好處是包的初始化順序終於有明確的標準化的定義了,壞處有兩點:

  1. 一起的程式如果依賴於特定的初始化順序,那麼在新版本會出問題
  2. 現在可以通過修改package的導入路徑(主要能改的部分是包的名字)和控制導入的包來做到讓A包始終在B包之前初始化,因此B包的初始化流程里可以對A包公開出來的介面或者數據進行修改,這樣做耦合性很高也不安全,尤其是B包如果是某個包含惡意代碼的包的話。

我們能做的只有遵守最佳實踐:不要依賴特定的包直接的初始化順序;以及在使用某個第三方庫前要仔細考量。

編譯器和runtime的變化

runtime的變化上,gc一如既往地得到了小幅度優化,現在對於gc壓力較大的程式來說gc延遲和記憶體占用都會有所減少。

cgo也有優化,現在cgo函數調用最大可以比原先快一個數量級。

編譯器的變化上比較顯著的是這個:PGO已經可以正式投入生產使用。使用教程

PGO可以帶來6%左右的性能提升,1.21憑藉PGO和上個版本的優化現在不僅沒有了泛型帶來的編譯速度減低,相比以前還有細微提升。

還有最後一個變化,這個和編譯器關係:現在沒有被使用的全局的map類型的變數(需要達到一定大小,且初始化的語句中沒有任何副作用會產生),現在編譯完成的程式里不會在包含這個變數。因為map本身占用記憶體且初始化需要花費一定時間(map越大花的時間越多)。這個好處是很實在的,既可以減小產生的二進位可執行文件的大小,又可以提升運行性能。但有個缺點,如果有什麼程式要依賴編譯好的可執行文件內部的某些數據的話,這個變更可能會帶來麻煩,普通用戶可以忽略這點。

新標準庫

這個版本添加了大把的新標準庫,一起來看看。

log/slog和testing/slogtest

官方提供的結構化日誌庫。

可以通過實現slog.Handler來定義自己的日誌處理器,可以把日誌轉換成json等格式。標準庫自帶了很多預定義的處理器,比如json的:

logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("hello", "count", 3)
// {"time":"2023-08-09T15:28:26.000000000+09:00","level":"INFO","msg":"hello","count":3}

簡單得說,就是個簡化版的zap,如果想使用最基礎的結構化日誌的功能,又不想引入zap這樣的庫,那麼slog是個很好的選擇。

testing/slogtest里有幫助函數用來測試自己實現的日誌處理器是否符合標準庫的要求。

slices和maps

golang.org/x/exp/slicesgolang.org/x/exp/maps引入了標準庫。

slices庫提供了排序、二分查找、拼接、增刪改查等常用功能,sort這個標準庫目前可以停止使用用slices來替代了。

maps提供了常見的對map的增刪改查拼接合併等功能。

兩個庫使用泛型,且針對golang的slice和map進行了細緻入微的優化,性能上比自己寫的版本有更多優勢,比標準庫sort更是有數量級的碾壓。

這兩個庫本來1.20就該被接收進標準庫了,但因為需要重新設計api和進行優化,所以拖到1.21了。

cmp

這個也是早該進入標準庫的,但拖到了現在。隨著slices、maps和新內置函數都進入了新版本,這個庫想不接收也不行了。

這個庫一共有三個東西:OrderedLessCompare

最重要的是Ordered,它是所有可以使用內置運算符進行比較的類型的集合。

LessOrdered顧名思義用來比大小的,且只能比Ordered類型的大小。

之所以還有單獨造出這兩個函數,是因為他們對Nan有檢查,比如:

// Less reports whether x is less than y.
// For floating-point types, a NaN is considered less than any non-NaN,
// and -0.0 is not less than (is equal to) 0.0.
func Less[T Ordered](x, y T) bool {
	return (isNaN(x) && !isNaN(y)) || x < y
}

所以在泛型函數里不知道要比較的數據的類型是不是有float的時候,用cmp里提供的函數是最安全的。這就是他倆存在的意義。

但如果可以100%確定沒有float存在,那麼就不應該用Less等,應該直接用運算符去比較,因為大家都看到,Less和直接比較相比效率是較低的。

已有的標準庫的變化

因為是速覽,所以我只挑重點說。

bytes

bytes.Buffer添加了AvailableBufferAvailable兩個方法,分別返回目前可用的buf切片和可用的長度。主要可以配合strconv.AppendInt來使用,直接把數據寫入buffer對應的記憶體里,可以提升性能。不要對AvailableBuffer返回的切片擴容,否則必然踩坑

context

新的context.WithoutCancel會把原來的context.Context複製一份,並去除cancel函數,這意味著原先被覆制的上下文取消了這個新的上下文也將繼續存在。例子:

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    newCtx := context.WithoutCancel(ctx)
    cancel()
    <-ctx.Done() // ok, ctx has cancled.
    <-newCtx.Done() // error: dead lock!
}

之所以會死鎖,是因為newCtx沒有被取消,Done返回的chan會永遠阻塞住。而且更根本的,newCtx無法被取消。

新增了context.WithDeadlineCausecontext.WithTimeoutCause,可以增加超時上下文被取消時的信息:

d := time.Now().Add(shortDuration)
ctx, cancel := context.WithDeadline(context.Background(), d, &MyError{"my message"})
cancel()
context.Cause(ctx) // --> &MyError{"my message"}

雖然不如context.WithCancelCause靈活,但也很實用。

crypto/sha256

現在在x86_64平臺上計算sha256會儘量利用硬體指令(simd和x86_64平臺的SHA256ROUND等指令),這帶來了3-4倍的性能提升。

net

現在golang在Linux上已經初步支持Multipath TCP。有關Multipath TCP的信息可以在這查閱:https://www.multipath-tcp.org/

reflect

ValueOf現在會根據逃逸分析把值分配在棧上,以前都是直接分配到堆上的。對於比較小的類型來說可以獲得10%以上的性能提升。利好很多使用反射的ORM框架。

新增了Value.Clear,對應第一節的clear內置函數,如果type不是map或者slice的話這個函數和其他反射的方法一樣會panic。

runtime

最值得一提的變化是新增了runtime.Pinner

它的能力是可以讓某個go的對象不會gc回收,一直到Unpin方法被調用。這個是為了方便cgo代碼里讓c使用go的對象而設計的。

不要濫用這個介面,如果想告訴gc某個對象暫時不能回收,應該正確使用runtime.KeepAlive

runtime/trace現在有了很大的性能提升,因此觀察程式行為的時候開銷更小,更接近程式真實的負載。

sync

添加了OnceFuncOnceValueOnceValues這三個幫助函數。主要是為了簡化代碼。

1.21前:

var initFlag sync.Once

func GetSomeThing() {
    initFlag.Do(func(){
        真正的初始化邏輯
    })
    // 後續處理
}

現在變成:

var doInit = sync.OnceFunc(func(){
    真正的初始化邏輯
})

func GetSomeThing() {
    doInit()
    // 後續處理
}

新代碼要簡單點。

OnceValueOnceValues是函數帶返回值的版本,支持一個和兩個返回值的函數。

errors

新增了errors.ErrUnsupported。這個錯誤表示當前操作系統、硬體、協議、或者文件系統不支持某種操作。

目前os,filepath,syscall,io里的一些函數已經會返回這個錯誤,可以用errors.Is(err, errors.ErrUnsupported)來檢查。

unicode

升級到了最新的Unicode 15.0.0。

平臺支持變化

新增了wasip1支持,這是一個對WASI(WebAssembly System Interface)的初步支持。

對於macOS,go1.21需要macOS 10.15 Catalina及以上版本。

龍芯上golang現在支持將代碼編譯為c的動態和靜態鏈接庫,基本上在龍芯上已經可以嘗試投入生產環境了。

總結

上面就是我認為需要關註一下的go1.21的新特性,不是所有的變化都羅列在其中。

比如實驗性支持的新的for-range語義我就沒寫,這個還在早期實驗階段,後續可能會被砍掉也可能行為上有極大變化,所以現在花精力關註不是很值得。

如果對其他變更有興趣,可以看完整的發版日誌

不過還是開頭說的那句話,先別急著在生產環境上升級,可以等到第一個patch版本出來了再說。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • >我們是[袋鼠雲數棧 UED 團隊](http://ued.dtstack.cn/),致力於打造優秀的一站式數據中台產品。我們始終保持工匠精神,探索前端道路,為社區積累並傳播經驗價值。 >本文作者:景明 ## 升級背景 目前公司產品有關 react 的工具版本普遍較低,其中 react router ...
  • 在現代的 Web 應用程式中,頁面訪問攔截是非常重要的一個方面。它可以用於確保用戶只能訪問他們有許可權的頁面,提高應用程式的安全性和用戶體驗。本篇博文將介紹如何使用 Vue 框架來實現頁面訪問攔截的功能。 ...
  • 本文主要做推薦系統淺析,主要介紹推薦系統的定義,推薦系統的基礎框架,簡單介紹設計推薦的相關方法以及架構。適用於部分對推薦系統感興趣的同學以及有相關基礎的同學,本人水平有限,歡迎大家指正。 ...
  • 不知道大家在開發的時候,有沒有想過(遇到)這些問題: 1、大家都是按需要開發,都是一個職級的同事,為什麼有些人的思路就很清晰,代碼也很整潔、易懂;而自己開發,往往不知道怎麼下手設計,寫完了也是bug一堆,codeReview的時候更是頻頻被懟... 2、感覺每天都是CURD,寫重覆的代碼,做類似的需... ...
  • ## 10.1、概念 - AOP(Aspect Oriented Programming)是一種設計思想,是軟體設計領域中的面向切麵編程 - AOP是面向對象編程(OOP)的一種補充和完善,OOP是縱向繼承機制,AOP是橫向抽取機制 - AOP能通過預編譯方式和運行期動態代理方式,實現在不修改源代碼 ...
  • 逆向過程分析與js代碼扣取 請求頭U-Sign數據 通過瀏覽器開發者工具可以看到返回數據的介面/youzy.dms.basiclib.api.college.query 添加XHR斷點刷新瀏覽器會自動進入斷點 查看具體請求數據,將斷點定位到r = r.then(t.shift(), t.shift( ...
  • **原文鏈接:** [go-zero 是如何做路由管理的?](https://mp.weixin.qq.com/s/uTJ1En-BXiLvH45xx0eFsA) go-zero 是一個微服務框架,包含了 web 和 rpc 兩大部分。 而對於 web 框架來說,路由管理是必不可少的一部分,那麼本文 ...
  • 考點介紹: 基本數據類型的長度、自動升級、JVM存儲和封裝類的相關考點,是校招常見考點。基礎考點不能出錯 一、考點題目 1、JAVA 中的幾種基本數據類型是什麼,各自占用多少位元組 解答:先瞭解2個單詞先:1、bit --位:位是電腦中......2、byte --位元組:位元組是...... 2、JA ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...