sync.Once的新扩展

在Go 1.21中, 增加了和sync.Once有关的三个函数。sync.Once本身实现就非常简单了,新增加的这三个函数到底是干啥的?让我们一起来看看。

sync.Once

我们常常使用sync.Once实现单例模式,它也非常的高效。

下面的代码是官方的一个例子,运行它可以看到onceBody函数只会被执行一次:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main
import (
"fmt"
"sync"
)
func main() {
var once sync.Once
onceBody := func() {
fmt.Println("Only once")
}
done := make(chan bool)
for i := 0; i < 10; i++ {
go func() {
once.Do(onceBody)
done <- true
}()
}
for i := 0; i < 10; i++ {
<-done
}
}

OnceFunc

1
func OnceFunc(f func()) func()

OnceFunc返回一个可以并发调用的函数,它可以被调用多次。即使返回的函数被调用多次,f也只会被调用一次。

下面的代码onceBody只被执行了一次:

1
2
3
4
5
6
7
8
9
10
func main() {
onceBody := func() {
fmt.Println("Only once")
}
foo := sync.OnceFunc(onceBody)
for i := 0; i < 10; i++ {
foo()
}
}

OnceValue

1
func OnceValue[T any](f func() T) func() T

OnceValue返回一个函数, 这个函数会返回f的返回值。多次调用都会返回同一个值。

下面的代码中,randvalue只会被执行一次,返回结果记做n的话, 并且每次调用bar函数都会返回n。bar可以并发的被调用。

1
2
3
4
5
6
7
randvalue := func() int {
return rand.Int()
}
bar := sync.OnceValue(randvalue)
for i := 0; i < 10; i++ {
fmt.Println(bar())
}

同时,可以看到在标准库中,泛型越来越被使用。

OnceValues

1
func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2)

OnceValuesOnceValue的功能类似,只不过返回两个参数,仅此而已。

综述一下:

  • 这三个函数返回的函数被调用的时候,分别返回0个、1个、2个返回值。
  • 返回的函数可以被并发的调用。
  • 如果f被执行的时候panic, 返回的函数被调用的时候也会panic,panic的值和f的panic一样。

稍微展开讲一下tuple。

如果想返回更多的返回值,自己模仿构造一个,或者把多个返回值封装到一个对象中。

很多年前,我记得看一篇文章介绍某种编程语言的tuple是硬编码的, 两个元素的tuple、三个元素的tuple、四个元素的tuple等,忘记是哪个语言了。
所以你也可以模仿它。

go-tuple就是这样实现的一个go语言的tuple,最多支持9个元素。

1
2
3
4
5
6
7
8
9
10
11
12
type T9
func FromArray9(arr [9]any) (T9[Ty1, Ty2, Ty3, Ty4, Ty5, Ty6, Ty7, Ty8, Ty9], error)
func FromArray9X(arr [9]any) T9[Ty1, Ty2, Ty3, Ty4, Ty5, Ty6, Ty7, Ty8, Ty9]
func FromSlice9(values []any) (T9[Ty1, Ty2, Ty3, Ty4, Ty5, Ty6, Ty7, Ty8, Ty9], error)
func FromSlice9X(values []any) T9[Ty1, Ty2, Ty3, Ty4, Ty5, Ty6, Ty7, Ty8, Ty9]
func New9(v1 Ty1, v2 Ty2, v3 Ty3, v4 Ty4, v5 Ty5, v6 Ty6, v7 Ty7, v8 Ty8, v9 Ty9) T9[Ty1, Ty2, Ty3, Ty4, Ty5, Ty6, Ty7, Ty8, Ty9]
func (t T9[Ty1, Ty2, Ty3, Ty4, Ty5, Ty6, Ty7, Ty8, Ty9]) Array() [9]any
func (t T9[Ty1, Ty2, Ty3, Ty4, Ty5, Ty6, Ty7, Ty8, Ty9]) GoString() string
func (t T9[Ty1, Ty2, Ty3, Ty4, Ty5, Ty6, Ty7, Ty8, Ty9]) Len() int
func (t T9[Ty1, Ty2, Ty3, Ty4, Ty5, Ty6, Ty7, Ty8, Ty9]) Slice() []any
func (t T9[Ty1, Ty2, Ty3, Ty4, Ty5, Ty6, Ty7, Ty8, Ty9]) String() string
func (t T9[Ty1, Ty2, Ty3, Ty4, Ty5, Ty6, Ty7, Ty8, Ty9]) Values() (Ty1, Ty2, Ty3, Ty4, Ty5, Ty6, Ty7, Ty8, Ty9)

顺便说一下,上面我介绍的Go 1.21新增的三个内建函数之一的clear,清除map的时候,只是把map置空,并不会shrink map, 你可以看例子: https://go.dev/play/p/quVwNvAZAGJ?v=gotip 或者相关讨论 https://github.com/golang/go/issues/56351