Go1.18,1.19 泛型从入门到放弃+Go1.21

发布历史

在这里插入图片描述

go1.20稳定版在2023-02-01正式发布了,秉持着不安装最新版本的原则,我将go由1.17升级为1.19版本。

顺手也将goland升级了下:
在这里插入图片描述

GO 1.18

一如既往,该版本保持了Go 1的兼容性。(As always, the release maintains the Go 1 promise of compatibility.)
我们希望几乎所有的Go程序都能像以前一样继续编译和运行。(We expect almost all Go programs to continue to compile and run as before.)

具体来说,包含以下几大特性:

  1. 泛型(Generics)
  2. 模糊测试(Fuzzing)
  3. 工作空间(Workspaces)
  4. TryLock()方法,虽然TryLock的正确使用确实存在,但它们是罕见的,而且使用TryLock的使用往往是mutex在特定使用中更深层次问题的标志。(GO官方多年来最终妥协的产物)
  5. 20% 性能提升:Apple M1、ARM64 和 PowerPC64 用户开心了!由于 Go 1.17 的寄存器 ABI 调用约定扩展到这些架构,Go 1.18 包括高达 20% 的 CPU 性能改进。
  6. go-get不再以模块感知模式构建或安装软件包。go-get现在致力于调整go.mod中的依赖关系。要在当前模块的上下文之外安装可执行文件(exe)的最新版本,请使用go install example.com/cmd@latest。
  7. Go 1.18明确了能修改go.mod、go.sum的命令只有三个:go get、go mod tidy和go mod download。这样开发人员就可以放心的在项目根目录下执行Go工具链提供的其他命令了。
  8. 新的net/nip包…

泛型

Therefore, while we encourage the use of generics where it makes sense, please use appropriate caution when deploying generic code in production.(因此,尽管我们鼓励在有意义的地方使用泛型,但在生产中部署泛型代码时请谨慎。)

引入

  1. 假设有两个求和函数SumInts 和 SumFloats并且逻辑差不多。
  2. 如果将来有其他类型,我们必须增加额外的函数,代码逻辑也类似。
  3. 有了泛型,只需要一个函数就可以实现以上两个函数的功能,而且可以方便扩展为支持其他相关类型,比如 map[int]float64 等。
func SumIntsOrFloats[K comparable, V int | float64](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

函数 SumIntsOrFloats 声明了两种参数:类型参数和普通函数参数。其中类型参数放在 [] 中,普通参数依然放在 () 中。

该函数的类型参数是:K comparable, V int | float64,其中 K、V 的名字不重要,分别表示某种类型,comparable 和 int | float64 是 K、V 类型的约束,即调用该方法时,K、V 允许的类型。comparable 是语言预定义的约束,即表示所有可比较类型,也就是说,K 可以是任意可比较类型。

而 V 的类型约束 int | float64 表示只允许是 int 或 float64,其他类型编译会报错。

该函数的普通参数:m map[K]V,这表明,m 是一个 map,它的 key 类型是 K,value 类型是 V。很显然,这两个是该函数「类型参数」定义的类型。

在 main 中增加如下调用:(同一个函数,支持 map[string]int 和 map[string]float64。)

fmt.Printf("泛型计算结果,Ints 结果: Floats 结果: %v\n", SumIntsOrFloats[string, int](ints), SumIntsOrFloats[string, float64](floats))
或
fmt.Printf("泛型计算结果,Ints 结果: Floats 结果: %v\n", SumIntsOrFloats(ints), SumIntsOrFloats(floats))

实际上 Go 会进行类型推断,即编译器会通过普通参数的类型推导出「类型参数」。不过,跟 Go 中其他类型自动推导类似,有些情况是无法自动推导的,这时候必须手动指定实际的类型参数。

概念

Go还引入了非常多全新的概念:

  • 类型形参 (Type parameter)
  • 类型实参(Type argument)
  • 类型形参列表( Type parameter list)
  • 类型约束(Type constraint)
  • 实例化(Instantiations)
  • 泛型类型(Generic type)
  • 泛型接收器(Generic receiver)
  • 泛型函数(Generic function)
    在这里插入图片描述

声明类型约束

在这里插入图片描述

上面没有将 int | float64 定义为一个命名约束,相当于约束字面量(或联合类型)。一般有两种场景会单独声明类型约束:

  1. 约束太长,比如有很多类型,直接写在函数中,会严重影响可读性
  2. 方便类型约束重用

将上面 V 的约束定义为单独的类型约束:(实际是接口,但不能作为单独类型使用

type Number interface{
  int | float64
}
func SumNumbers[K comparable, V Number](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

Constraint(约束)

约束的意思是限定范围, constraint的作用就是限定范围, 将T限定在某种范围内

而常用的范围, 自然会想到的有:

  1. an(interface{})
  2. Interger(所有int)
type Integer interface {
	Signed | Unsigned
}

type Signed interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64
}
  1. Float(所有Float)
  2. comparable(所有可以比较的类型, 我们可以给所有可以比较的类型定制一些方法)

    这些约束, 不是被官方定义为内置类型, 就是被涵盖在了constraints包内。

其中~主要用来表示底层类型一致,例如type MyInt int 和int底层都是int类型,如果不使用~,那么类型实例化时就不能使用MyInt类型。
在这里插入图片描述

泛型实例

类型形参的互相套用

任何泛型类型都必须传入类型实参实例化才可以使用

type WowStruct[T int | float32, S []T] struct {
    Data     S
    MaxValue T
    MinValue T
}
var ws WowStruct[int, []int]
// 泛型类型 WowStuct[T, S] 被实例化后的类型名称就叫 WowStruct[int, []int]

经过实例化之后 WowStruct[T,S] 的定义类似如下:

// 一个存储int类型切片,以及切片中最大、最小值的结构体
type WowStruct[int, []int] struct {
    Data     []int
    MaxValue int
    MinValue int
}

因为 S 的定义是 []T ,所以 T 一定决定了的话 S 的实参就不能随便乱传了,下面这样的代码是错误的:

// 错误。S的定义是[]T,这里T传入了实参int, 所以S的实参应当为 []int 而不能是 []float32
ws := WowStruct[int, []float32]{
        Data:     []float32{1.0, 2.0, 3.0},
        MaxValue: 3,
        MinValue: 1,
    }
几种语法错误
  1. 定义泛型类型的时候,基础类型不能只有类型形参,如下:

    // 错误,类型形参不能单独使用
    type CommonType[T int|string|float32] T
    
    // 正确的定义CommonType
    type CommonType interface{
    	int|string|float32
    }
    
    func Sum[T CommonType](number T)bool{
    }
    
  2. 当类型约束的一些写法会被编译器误认为是表达式时会报错。如下:

    //✗ 错误。T *int会被编译器误认为是表达式 T乘以int,而不是int指针
    type NewType[T *int] []T
    // 上面代码再编译器眼中:它认为你要定义一个存放切片的数组,数组长度由 T 乘以 int 计算得到
    type NewType [T * int][]T 
    
    //✗ 错误。和上面一样,这里不光*被会认为是乘号,| 还会被认为是按位或操作
    type NewType2[T *int|*float64] []T 
    
    //✗ 错误
    type NewType2 [T (int)] []T 
    

    为了避免这种误解,解决办法就是给类型约束包上 interface{} 或加上逗号消除歧义.

    type NewType[T interface{*int}] []T
    type NewType2[T interface{*int|*float64}] []T 
    
    // 如果类型约束中只有一个类型,可以添加个逗号消除歧义
    type NewType3[T *int,] []T
    
    //✗ 错误。如果类型约束不止一个类型,加逗号是不行的
    type NewType4[T *int|*float32,] []T 
    

    因为上面逗号的用法限制比较大,推荐统一用 interface{} 解决问题。

特殊的泛型类型(无意义)
type SB[T int | string] int

var a SB[int] = 123     // 编译正确
var b SB[string] = 123  // 编译正确
var c SB[string] = "hello" // 编译错误,因为"hello"不能赋值给底层类型int

因为类型定义是type SB[T int|string] int,所以无论传入什么类型实参,实例化后的新类型的底层类型都是 int 。所以int类型的数字123可以赋值给变量a和b,但string类型的字符串 “hello” 不能赋值给c。

泛型类型的嵌套
// 定义泛型类型 Slice[T]
type Slice[T int|string|float32|float64] []T

// ✗ 错误。泛型类型Slice[T]的类型约束中不包含uint, uint8
type UintSlice[T uint|uint8] Slice[T]  

// ✓ 正确。基于泛型类型Slice[T]定义了新的泛型类型 FloatSlice[T] 。FloatSlice[T]只接受float32和float64两种类型
type FloatSlice[T float32|float64] Slice[T] 

// ✓ 正确。基于泛型类型Slice[T]定义的新泛型类型 IntAndStringSlice[T]
type IntAndStringSlice[T int|string] Slice[T]  
// ✓ 正确 基于IntAndStringSlice[T]套娃定义出的新泛型类型
type IntSlice[T int] IntAndStringSlice[T] 

// 在map中套一个泛型类型Slice[T]
type WowMap[T int|string] map[string]Slice[T]
// 在map中套Slice[T]的另一种写法
type WowMap2[T Slice[int] | Slice[string]] map[string]T
类型约束的两种选择
type WowStruct[T int|string] struct {
    Name string
    Data []T
}type WowStruct2[T []int|[]string] struct {
    Name string
    Data T
}

// 这种情况的时候,使用前一种写法会更好:
type WowStruct3[T int | string] struct {
    Data     []T
    MaxValue T
    MinValue T
}

这里推荐全用第一种,提高可读性。

匿名结构体不支持泛型
// 只能这样用
testCase := struct {
        caseName string
        got      int
        want     int
    }{
        caseName: "test OK",
        got:      100,
        want:     100,
    }
泛型接收者
type MySlice[T int | float32] []T

func (s MySlice[T]) Sum() T {
    var sum T
    for _, value := range s {
        sum += value
    }
    return sum
}

var s MySlice[int] = []int{1, 2, 3, 4}
fmt.Println(s.Sum()) // 输出:10

Go的方法并不支持泛型。

但是因为receiver支持泛型, 所以如果想在方法中使用泛型的话,通过receiver使用类型形参:

type A[T int | float32 | float64] struct {
}

// 方法可以使用类型定义中的形参 T 
func (receiver A[T]) Add(a T, b T) T {
    return a + b
}

// 用法:
var a A[int]
a.Add(1, 2)

var aa A[float32]
aa.Add(1.0, 2.0)
型类型定义的变量不能使用类型断言
func (receiver Queue[T]) Put(value T) {
    // Printf() 可输出变量value的类型(底层就是通过反射实现的)
    fmt.Printf("%T", value) 

    // 通过反射可以动态获得变量value的类型从而分情况处理
    v := reflect.ValueOf(value)

    switch v.Kind() {
    case reflect.Int:
        // do something
    case reflect.String:
        // do something
    }

    // ...
}

为了避免使用反射而选择了泛型,结果到头来又为了一些功能在在泛型中使用反射,是不是真的需要用泛型?

泛型函数
func Add[T int | float32 | float64](a T, b T) T {
    return a + b
}

和泛型类型一样,泛型函数也是不能直接调用的,要使用泛型函数的话必须传入类型实参之后才能调用。

Add[int](1,2) // 传入类型实参int,计算结果为 3
Add[float32](1.0, 2.0) // 传入类型实参float32, 计算结果为 3.0

Add[string]("hello", "world") // 错误。因为泛型函数Add的类型约束中并不包含string

Go还支持类型实参的自动推导:

Add(1, 2)  // 1,2是int类型,编译请自动推导出类型实参T是int
Add(1.0, 2.0) // 1.0, 2.0 是浮点,编译请自动推导出类型实参T是float32

匿名函数不支持泛型。

变得复杂的接口

Go支持将类型约束单独拿出来定义到接口中,从而让代码更容易维护,

type IntUintFloat interface {
    int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64
}

type Slice[T IntUintFloat] []T

接口嵌套:

type Int interface {
    int | int8 | int16 | int32 | int64
}

type Uint interface {
    uint | uint8 | uint16 | uint32
}

type Float interface {
    float32 | float64
}

type Slice[T Int | Uint | Float] []T  // 使用 '|' 将多个接口类型组合

type SliceElement interface {
    Int | Uint | Float | string // 组合了三个接口类型并额外增加了一个 string 类型
}

type Slice[T SliceElement] []T
~指定底层类型
var s1 Slice[int] // 正确 

type MyInt int
var s2 Slice[MyInt] // ✗ 错误。MyInt类型底层类型是int但并不是int类型,不符合 Slice[T] 的类型约束

泛型类型 Slice[T] 允许的是 int 作为类型实参,而不是 MyInt (虽然 MyInt 类型底层类型是 int ,但它依旧不是 int 类型)。

Go新增了一个符号 ~ ,在类型约束中使用类似 ~int 这种写法的话,就代表着不光是 int ,所有以 int 为底层类型的类型也都可用于实例化。

type Int interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64
}

type Uint interface {
    ~uint | ~uint8 | ~uint16 | ~uint32
}
type Float interface {
    ~float32 | ~float64
}

type Slice[T Int | Uint | Float] []T 

var s Slice[int] // 正确

type MyInt int
var s2 Slice[MyInt]  // MyInt底层类型是int,所以可以用于实例化

type MyMyInt MyInt
var s3 Slice[MyMyInt]  // 正确。MyMyInt 虽然基于 MyInt ,但底层类型也是int,所以也能用于实例化

type MyFloat32 float32  // 正确
var s4 Slice[MyFloat32]
  1. ~后面的类型不能为接口
  2. ~后面的类型必须为基本类型
接口的定义

在Go1.18之前,Go官方对 接口(interface) 的定义是:接口是一个方法集(method set)
Go1.18开始就是依据这一点将接口的定义正式更改为了 类型集(Type set)

类型约束 指定了类型形参可接受的类型集合,只有属于这个集合中的类型才能替换形参用于实例化。

那么从Go1.18开始 接口实现(implement) 的定义自然也发生了变化:

当满足以下条件时,我们可以说 类型 T 实现了接口 I ( type T implements interface I):

  1. T 不是接口时:类型 T 是接口 I 代表的类型集中的一个成员 (T is an element of the type set of I)
  2. T 是接口时: T 接口代表的类型集是 I 代表的类型集的子集(Type set of T is a subset of the type set of I)
类型的并集
type Uint interface {  // 类型集 Uint 是 ~uint 和 ~uint8 等类型的并集
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}
类型的交集
type AllInt interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint32
}

type Uint interface {
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}

type A interface { // 接口A代表的类型集是 AllInt 和 Uint 的交集
    AllInt
    Uint
}

type B interface { // 接口B代表的类型集是 AllInt 和 ~int 的交集
    AllInt
    ~int
}
空集

没有任何一种类型属于空集

type Bad interface {
    int
    float32 
} // 类型 int 和 float32 没有相交的类型,所以接口 Bad 代表的类型集为空
空接口和 any

空接口代表了所有类型的集合,

  1. 虽然空接口内没有写入任何的类型,但它代表的是所有类型的集合,而非一个 空集
  2. 类型约束中指定 空接口 的意思是指定了一个包含所有类型的类型集,并不是类型约束限定了只能使用 空接口 来做类型形参

所以,go1.18将interface{}定义为了any

所以从 Go 1.18 开始,所有可以用到空接口的地方其实都可以直接替换为any,

# 将当前目录下边的所有文件中的interface{}替换为any
gofmt -w -r 'interface{} -> any' ./...
comparable(可比较) 和 可排序(ordered)
  1. comparable 比较容易引起误解的一点是很多人容易把他与可排序搞混淆。
  2. 可比较指的是 可以执行 != == 操作的类型,并没确保这个类型可以执行大小比较( >,<,<=,>= )

虽然可以直接使用官方包 golang.org/x/exp/constraints ,但因为这个包属于实验性质的 x 包,今后可能会发生非常大变动,所以并不推荐直接使用

接口两种类型
type ReadWriter interface {
    ~string | ~[]rune

    Read(p []byte) (n int, err error)
    Write(p []byte) (n int, err error)
}

// 类型 StringReadWriter 实现了接口 Readwriter
type StringReadWriter string 

func (s StringReadWriter) Read(p []byte) (n int, err error) {
    // ...
}

func (s StringReadWriter) Write(p []byte) (n int, err error) {
 // ...
}

//  类型BytesReadWriter 没有实现接口 Readwriter
type BytesReadWriter []byte 

func (s BytesReadWriter) Read(p []byte) (n int, err error) {
 ...
}

func (s BytesReadWriter) Write(p []byte) (n int, err error) {
 ...
}

接口类型 ReadWriter 代表了一个类型集合,所有以 string 或 []rune 为底层类型,并且实现了 Read() Write() 这两个方法的类型都在 ReadWriter 代表的类型集当中。

Go1.18开始将接口分为了两种类型:

  1. 基本接口(Basic interface):接口定义中如果只有方法的话,那么这种接口被称为基本接口(Basic interface)。(1.18之前版本)
  2. 一般接口(General interface):如果接口内不光只有方法,还有类型的话,这种接口被称为 一般接口(General interface) ,一般接口类型不能用来定义变量,只能用于泛型的类型约束中。(1.18+)
泛型接口
  1. 形式1

    type DataProcessor[T any] interface {
        Process(oriData T) (newData T)
        Save(data T) error
    }
    
    type CSVProcessor struct {
    }
    
    // 注意,方法中 oriData 等的类型是 string
    func (c CSVProcessor) Process(oriData string) (newData string) {
        ....
    }
    
    func (c CSVProcessor) Save(oriData string) error {
        ...
    }
    
    // CSVProcessor实现了接口 DataProcessor[string] ,所以可赋值
    var processor DataProcessor[string] = CSVProcessor{}  
    processor.Process("name,age\nbob,12\njack,30")
    
  2. 形式2

    type DataProcessor2[T any] interface {
        int | ~struct{ Data interface{} }
    
        Process(data T) (newData T)
        Save(data T) error
    }
    
    // JsonProcessor 实现了接口 DataProcessor2[string] 的两个方法,同时底层类型是 struct{ Data interface{} }。所以实现了接口 DataProcessor2[string]
    type JsonProcessor struct {
        Data interface{}
    }
    
    func (c JsonProcessor) Process(oriData string) (newData string) {
    
    }
    
    func (c JsonProcessor) Save(oriData string) error {
    
    }
    
    // 错误。DataProcessor2[string]是一般接口不能用于创建变量
    var processor DataProcessor2[string]
    
    // 正确,实例化之后的 DataProcessor2[string] 可用于泛型的类型约束
    type ProcessorList[T DataProcessor2[string]] []T
    
    // 正确,接口可以并入其他接口
    type StringProcessor interface {
        DataProcessor2[string]
    
        PrintString()
    }
    
    // 错误,带方法的一般接口不能作为类型并集的成员
    type StringProcessor interface {
        DataProcessor2[string] | DataProcessor2[[]byte]
    
        PrintString()
    }
    
    

    只有实现了 Process(string) string 和 Save(string) error 这两个方法,并且以 int 或 struct{ Data interface{} } 为底层类型的类型才算实现了这个接口
    一般接口(General interface) 不能用于变量定义只能用于类型约束,所以接口 DataProcessor2[string] 只是定义了一个用于类型约束的类型集

接口定义的限制规则
  1. 用 | 连接多个类型的时候,类型之间不能有相交的部分(即必须是不交集),但是相交的类型中是接口的话,则不受这一限制:
type MyInt int

// 错误,MyInt的底层类型是int,和 ~int 有相交的部分
type _ interface {
    ~int | MyInt
}

type MyInt int

type _ interface {
    ~int | interface{ MyInt }  // 正确
}
  1. 类型的并集中不能有类型形参
type MyInf[T ~int | ~string] interface {
    ~float32 | T  // 错误。T是类型形参
}
  1. 接口不能直接或间接地并入自己
type Bad interface {
    Bad // 错误,接口不能直接并入自己
}
  1. 接口的并集成员个数大于一的时候不能直接或间接并入 comparable 接口
type OK interface {
    comparable // 正确。只有一个类型的时候可以使用 comparable
}

type Bad1 interface {
    []int | comparable // 错误,类型并集不能直接并入 comparable 接口
}
  1. 带方法的接口(无论是基本接口还是一般接口),都不能写入接口的并集中:
type _ interface {
    ~int | ~string | error // 错误,error是带方法的接口(一般接口) 不能写入并集中
}

type DataProcessor[T any] interface {
    ~string | ~[]byte

    Process(data T) (newData T)
    Save(data T) error
}

// 错误,实例化之后的 DataProcessor[string] 是带方法的一般接口,不能写入类型并集
type _ interface {
    ~int | ~string | DataProcessor[string] 
}

type Bad[T any] interface {
    ~int | ~string | DataProcessor[T]  // 也不行
}

小结

  1. 泛型应用范围没有想象中的那么大,比如想写个队列,写个链表、栈、堆之类的数据结构可以用泛型:
// 这里类型约束使用了空接口,代表的意思是所有类型都可以用来实例化泛型类型 Queue[T] (关于接口在后半部分会详细介绍)
type Queue[T interface{}] struct {
    elements []T
}
 
 
// 将数据放入队列尾部
func (q *Queue[T]) Put(value T) {
    q.elements = append(q.elements, value)
}
  1. 泛型提高了代码编写的复杂度,是接口的一场革命,并且也会降低执行效率(泛型推导)。
  2. 听官方的劝,别乱用泛型。

工作空间workspace

工作区模式(Workspace mode),可不是之前 GOPATH 时代的 Workspace,而是希望在本地开发时支持多 Module。

有如下目录结构:
在这里插入图片描述
go buildgo run时,显示没有找到syncMap。

go: finding module for package github.com/Generalzy/common/syncMap

解决方法1:添加replace选项(这时候goland已经提示,可以将syncMap加入到工作区)

在这里插入图片描述

解决方法2:工作区模式

通过 go helpgo help work 可以看到,新增了 work 相关的命令:

在这里插入图片描述

到最外边初始化工作区:
go work init 子模块1 子模块2 ...

注意几点:

  1. 多个子模块应该在一个目录下。(这不是必须的,但更好管理,否则 go work init 需要提供正确的子模块路径)
  2. go work init 需要在最外边的目录执行;
  3. go work init 之后跟上需要本地开发的子模块目录名;

在这里插入图片描述
在这里插入图片描述
删除掉mod中的replace语句,依旧可以正常执行。

注意,go.work 不需要提交到 Git 中,因为它只是你本地开发使用的。

如果想要禁用 workspace,可以通过 -workfile=off 实现,比如:go run -workfile=off main.go 或 go build -workfile=off,这样运行又报错了。但通过这种方式,可以验证依赖包提交到 github 上之后的情况。

go work use 添加新的模块到工作区

go work use 模块目录3 添加一个模块到工作区

go 1.19

use (
    ./hello
    ./example
    ./模块目录3
)

// replaces 命令与 go.mod 指令相同,用于替换项目中依赖的仓库地址
replace (
    github.com/link1st/example => ./example
)

go work edit 用于编辑 go.work 文件,可以使用 edit 命令编辑和手动编辑 go.work 文件效果是相同的.

go work edit -fmt go.work 重新格式化 go.work 文件
go work edit -replace=github.com/link1st/example=./example go.work 替换代码模块
go work edit -dropreplace=github.com/link1st/example 删除替换代码模块
go work edit -use=./example go.work 添加新的模块到工作区
go work edit -dropuse=./example go.work 从工作区中删除模块

go work sync 将工作区的构建列表同步到工作区的模块

全局禁用

export GOWORK=off

结论

go.work 文件优先级高于 go.mod 中定义在同时使用 go.work 和 go.mod replace 功能的的时候分别指定不同的代码仓库路径,go.work 优先级高于 go.mod 中定义.

TryLock

有时我们希望尝试获取锁,如果获取到了则继续执行,如果获取不到,我们也不想阻塞住,而是去调用其它的逻辑,这个时候我们就想要 TryLock 方法:即尝试获取锁,获取不到也不堵塞。

这个需求,2013 年就有人提出,但官方没有采纳。2018 年又有人提出也没有下文,直到 2021 年 4 月,有人再次提出,同时也给出了标准库中需要的场景。

对此go的负责人rsc认为 TryLock 会鼓励设计者对锁进行不精确的思考,这可能最终会成为 race(竞态) 的根源。同时,他认为仅为 http2 提供 TryLock 不值得,希望有更具说服力的案例。

最终1.18版本给出了3个TryLock方法,不过,rsc 建议,大家尽量别使用它。

Go 1.19

在本次 Go1.19 的新版本更新中,新特性是比较少的。其中主要的原因还是泛型的各项工作给 Go 团队带来了不少的工作量。它的大部分变化在于工具链、运行时和库的实现。

Go 1.19 重要的变化有:

  1. Go 内存模型.
  2. 随着内存模型的更新,Go 1.19 在 sync/atomic 包引入了新的类型:types Bool, Int32, Int64, Uint32, Uint64, Uintptr 和 Pointer 等,这让原子值的使用更方便。
  3. 现在,编译器使用跳转表来实现大整数和字符串的switch语句。对于switch语句的性能改进因情况而异,但可能达到快约20%。 (仅适用于GOARCH=amd64和GOARCH=arm64)
  4. 工具方面,文档格式增强。文档注释中添加了对链接、列表和更清晰标题的支持。
  5. runtime 方面,最值得关注的变化就是增加了 runtime/debug.SetMemoryLimit,可以限制 Go 的内存使用。
  6. 从 Go1.19 起,构建约束 unix 现在可以在 //go:build 行中被识别,能够起到配套的约束作用。
  7. 在 Go 1.19 起增加了对 Linux 上 Loongson 64 位架构的支持(GOOS=linux,GOARCH=loong64)。
  8. 包的改动…

Go 1.20

最新的 Go 版本 1.20 在Go 1.19发布六个月后发布。它的大部分变化在于工具链、运行时和库的实现。

  1. 垃圾收集器经过重组,从而减少了内存使用量并将性能提高了 2%。
  2. Go 构建 1.20 比 1.19 快 20%。这意味着对最近的速度降低感到失望的人们可以重新享受构建 Go 应用程序的速度。
  3. "Go 1.17增加了从切片到数组指针的转换。Go 1.20将其扩展,允许从切片到数组的转换:给定一个切片x,现在可以写成[4]byte(x),而不是*(*[4]byte)(x)。
  4. unsafe包定义了三个新函数SliceData、String和StringData。加上Go 1.17的Slice函数,这些函数现在提供了完整的能力来构造和拆解切片和字符串值,而不依赖于它们的确切表示。
  5. 规范现在定义了结构体值按字段一次比较,考虑字段在结构体类型定义中出现的顺序,并在第一次不匹配时停止。先前可能已阅读规范,好像在第一次不匹配之后需要比较所有字段。同样,规范现在定义了数组值按递增的索引顺序逐个比较。在这两种情况下,差异影响是否必须触发某些比较。现有程序保持不变:新的规范措辞描述了实现始终以来的行为。
  6. 可比较的类型(例如普通接口)现在可以满足可比较的约束,即使类型参数不是严格可比较的(比较可能在运行时引发panic)。这使得可以用非严格可比较的类型参数(例如接口类型或包含接口类型的复合类型)实例化受可比较约束的类型参数,例如用户定义的通用映射键的类型参数。"
  7. Go 1.20 是可在 Windows 7、8、Server 2008 和 Server 2012 的任何版本上运行的最后一个版本。Go 1.21 至少需要 Windows 10 或 Server 2016。

Go 1.21

  1. 最新的 Go 版本 1.21 比Go 1.20发布六个月后发布。它的大部分变化在于工具链、运行时和库的实现。
  2. Go 1.21 为该语言添加了三个新的内置函数。
    1. 新函数min和max计算max固定数量的给定参数的最小(或最大,对于 )值。
    2. 新函数clear删除映射中的所有元素或将切片的所有元素归零。
  3. 现在可以更精确地指定包初始化顺序。新算法是:
    • 按导入路径对所有包进行排序。
    • 重复直到包列表为空:
      • 找到列表中所有导入均已初始化的第一个包。
      • 初始化该包并将其从列表中删除。
  4. Go 1.21包含了我们考虑在未来版本中进行的一项语言更改的预览:将for循环变量更改为每次迭代而不是每个循环,以避免意外的共享错误。
  5. 新的log/slog包:新的log/slog包提供带有级别的结构化日志记录。结构化日志记录以键值对形式发出,以实现对大量日志数据的快速、准确处理。该包支持与流行的日志分析工具和服务集成。
  6. 新的testing/slogtest包:新的testing/slogtest包可帮助验证slog.Handler实现。
  7. 新的slices包:新的slices包提供许多对切片执行的常见操作,使用通用函数,这些函数适用于任何元素类型的切片。
  8. 新的maps包:新的maps包提供对映射执行的几个常见操作,使用通用函数,这些函数适用于任何键或元素类型的映射。
  9. 新的cmp包:新的cmp包定义了类型约束Ordered和两个新的通用函数Less和Compare,这些函数在有序类型中非常有用。
    10.新的WithoutCancel函数返回原始上下文被取消时不被取消的上下文的副本。新的WithDeadlineCause和WithTimeoutCause函数提供了在截止日期或计时器过期时设置上下文取消原因的方式。可以使用Cause函数检索原因。新的AfterFunc函数注册一个在上下文被取消后运行的函数。

min&max

内置函数min和max分别计算有序类型的固定数量参数的最小值或最大值。必须至少有一个参数。

与运算符相同的类型规则适用:对于有序参数x和y,如果x + y有效,则min(x, y)有效,而min(x, y)的类型是x + y的类型(max同理)。如果所有参数都是常量,则结果也是常量。

var x, y int
m := min(x)                 // m == x
m := min(x, y)              // m is the smaller of x and y
m := max(x, y, 10)          // m is the larger of x and y but at least 10
c := max(1, 2.0, 10)        // c == 10.0 (floating-point kind)
f := max(0, float32(x))     // type of f is float32
var s []string
_ = min(s...)               // invalid: slice arguments are not permitted
t := max("", "foo", "bar")  // t == "foo" (string kind)

对于数字参数,假设所有 NaN 都相等,min和max是可交换的和结合的:

min(x, y) == min(y, x) 
min(x, y, z) == min(min(x, y), z) == min(x, min(y, z))

对于浮点参数的负零、NaN 和无穷大,适用以下规则:

   x        y    min(x, y)    max(x, y)

  -0.0    0.0         -0.0          0.0    // negative zero is smaller than (non-negative) zero
  -Inf      y         -Inf            y    // negative infinity is smaller than any other number
  +Inf      y            y         +Inf    // positive infinity is larger than any other number
   NaN      y          NaN          NaN    // if any argument is a NaN, the result is a NaN

对于字符串参数,min的结果是具有最小值(或max的结果是最大值)的第一个参数,按字节逐字典比较:

min(x, y)    == if x <= y then x else y
min(x, y, z) == min(min(x, y), z)

clear

内置函数clear接受一个map、切片或类型参数类型的参数,并删除或将所有元素归零。

Call        Argument type     Result

clear(m)    map[K]T           deletes all entries, resulting in an
                              empty map (len(m) == 0)

clear(s)    []T               sets all elements up to the length of
                              s to the zero value of T

clear(t)    type parameter    参考下文

如果clear的参数类型是type parameter,则其类型集中的所有类型必须是映射或切片,并且clear执行与实际类型参数对应的操作。

如果映射或切片为nil,则clear不执行任何操作。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Generalzy

文章对您有帮助,倍感荣幸

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值