go1.22的一些关键改动及代码演示
参考汇总文章
Go1.22 正式发布!包含语言变化、性能提高、标准库变动等重要特性
在电脑中安装多个版本的golang
由于我的电脑安装的是go的1.21版本,1.22版本改动很大,如果工作中部署的项目dockerFile中指定的镜像的go版本比1.22低的话,有一些语法会编译不通过,所以我在官方下载了.gz格式的go,然后解压到本地,再使用alias命令做了一个简单的别名,简单的演示一下go1.22的改动即可。
至于工作中是否有必要将go的版本升级到最新的1.22需要跟公司的同事一起商量。
下载后解压命令
tar -zxvf go1.22.1.darwin-amd64.tar.gz
设置别名
~~~完事后记得重启下终端 或者 source ~/.bash_profile
~~~
语言层面解决“惰性计算”问题
go1.21及之前版本遇到的并发情况下的“惰性计算“问题及解决方案
package main import ( "fmt" "testing" ) // 有惰性计算问题 func TestT1(t *testing.T) { done := make(chan bool) values := []string{"a", "b", "c"} for _, v := range values { go func() { // Notice 使用go1.21编译 有惰性计算问题 打印: /* v: c v: c v: c */ fmt.Println("v: ", v) done <- true }() } // wait for all goroutines to complete before exiting for _ = range values { <-done } } // 解决方案1 func TestT2(t *testing.T) { done := make(chan bool) values := []string{"a", "b", "c"} for _, v := range values { // Notice 每次for循环 不会为v新申请内存 // Notice 使用go1.21编译 有惰性计算问题 这样解决: // 在for循环中使用一个临时变量接收v,相当于重新申请一块内存 currV := v go func() { /* v: c v: b v: a */ fmt.Println("v: ", currV) done <- true }() } // wait for all goroutines to complete before exiting for _ = range values { <-done } } // 解决方案2 func TestT3(t *testing.T) { done := make(chan bool) values := []string{"a", "b", "c"} for _, v := range values { // Notice 每次for循环 不会为v新申请内存 // Notice 使用go1.21编译 有惰性计算问题 这样解决: // 将v当作参数传到子协程这个匿名函数中 go func(currV string) { /* v: c v: a v: b */ fmt.Println("v: ", currV) done <- true }(v) } // wait for all goroutines to complete before exiting for _ = range values { <-done } }
go1.22从语言层面解决了“惰性计算”问题
注意:下面的代码需要使用go1.22的编译器才能出来效果:
package main import "fmt" func main() { done := make(chan bool) values := []string{"a", "b", "c"} for _, v := range values { // Notice 每次for循环都会为v重新申请一块内存 go func() { // Notice 使用go1.22编译 解决了惰性计算问题 打印: /* v: b v的地址: 0xc000090030 v: a v的地址: 0xc000090020 v: c v的地址: 0xc000090040 */ fmt.Println("v: ", v, " v的地址: ", &v) done <- true }() } // wait for all goroutines to complete before exiting for _ = range values { <-done } }
go1.22的range关键字支持整型数据
package main import "fmt" func main() { // Notice 下面的代码只能在go1.22版本运行,以下的版本会报错 for i := range 10 { fmt.Println(i) } fmt.Println("------------------------------") /* 0 1 2 3 4 5 6 7 8 9 */ }
go1.22新的高效的math/rand/v2库
参考文章:Go1.22 新特性:新的 math/rand/v2 库,更快更标准!
新老版本的rand模块效率对比
package _rand_tests import ( "math/rand" randV2 "math/rand/v2" "testing" ) const Max = 1e9 func BenchmarkRand(b *testing.B) { for i := 0; i < b.N; i++ { rand.Intn(Max) } /* goos: darwin goarch: amd64 pkg: go122Project/_rand_tests cpu: VirtualApple @ 2.50GHz BenchmarkRand BenchmarkRand-10 70538617 17.08 ns/op PASS */ } func BenchmarkRandV2(b *testing.B) { for i := 0; i < b.N; i++ { randV2.IntN(Max) } /* goos: darwin goarch: amd64 pkg: go122Project/_rand_tests cpu: VirtualApple @ 2.50GHz BenchmarkRandV2 BenchmarkRandV2-10 130344816 8.751 ns/op PASS */ }
删除Seed与Read方法
slices包新增Concat函数-切片拼接
package _slices_tests import ( "fmt" "slices" "testing" ) // go1.22之前的切片拼接 func TestS1(t *testing.T) { s1 := []string{"煎鱼", "咸鱼", "摸鱼"} s2 := []string{"炸鱼", "水鱼", "煎鱼"} s3 := []string{"whw", "naruto", "sasuke"} ret := append(s1, s2...) ret = append(ret, s3...) fmt.Println(ret) // [煎鱼 咸鱼 摸鱼 炸鱼 水鱼 煎鱼 whw naruto sasuke] } // go1.22新的切片拼接 // Notice 需要确保传入的切片类型都是一致的!!! func TestS2(t *testing.T) { s1 := []string{"煎鱼", "咸鱼", "摸鱼"} s2 := []string{"炸鱼", "水鱼", "煎鱼"} s3 := []string{"whw", "naruto", "sasuke"} ret := slices.Concat(s1, s2, s3) fmt.Println("ret: ", ret) // [煎鱼 咸鱼 摸鱼 炸鱼 水鱼 煎鱼 whw naruto sasuke] a1 := []int{1, 3, 4} a2 := []int{22, 22, 22} fmt.Println(">> ", slices.Concat(a1, a2)) // [1 3 4 22 22 22] }
slices其他的改动
Go1.22 新特性:Slices 变更 Concat、Delete、Insert 等函数,对开发挺有帮助!
~~~
clear函数清空slice的坑!
package main import ( "fmt" ) type S1 struct { Name string Age int } func main() { s1 := S1{Name: "s1", Age: 22} s2 := S1{Name: "s2", Age: 23} s3 := S1{Name: "s3", Age: 24} // slice slice1 := []*S1{&s1, &s2, &s3} fmt.Println("slice1: >>> ", len(slice1), slice1) // 3 [0x1400012c0a8 0x1400012c0c0 0x1400012c0d8] // _clear slice clear(slice1) // Notice clear切片后 注意!!!之前的元素会被 nil 替代!!! // Notice 对于切片 clear感觉还不如直接重新赋值一个新的空切片!!! fmt.Println("slice1_clear后: >>> ", len(slice1), slice1) // 3 [<nil> <nil> <nil>] // map 清空map不会有零值 map1 := map[string]*S1{ "s1": &s1, "s2": &s2, "s3": &s3, } fmt.Println("map1: >>> ", len(map1), map1) // 3 map[s1:0x1400012c0a8 s2:0x1400012c0c0 s3:0x1400012c0d8] // _clear map clear(map1) fmt.Println("map1_clear后: >>> ", len(map1), map1) // 0 map[] // Notice 测试下 clear后能不能再赋值 map1["ss1"] = &s1 fmt.Println("map1_clear后再赋值: >>> ", len(map1), map1) // 1 map[ss1:0x140000b40a8] }
~~~