In Go 1.21, three functions related to sync.Once have been added. sync.Once itself is very simple to implement, but what do these three new functions do? Let’s take a look.

sync.Once

We often use sync.Once to implement the singleton pattern, which is also very efficient.

The code below is an official example, run it and you can see that the onceBody function will only be executed once.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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 returns a function that can be called concurrently, and it can be called more than once. Even if the returned function is called more than once, f will only be called once.

The following code, onceBody, is only executed once.

1
2
3
4
5
6
7
8
9
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 returns a function which returns the return value of f. Multiple calls will return the same value.

In the code below, randvalue will only be executed once, returning the result as n, and each call to the bar function will return n. bar can be called concurrently.

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())
}

At the same time, it can be seen that generics are increasingly being used in the standard library.

OnceValues

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

OnceValues and OnceValue have a similar function, except that they return two parameters, that’s all.

To summarise:

  • The three functions returned return 0, 1, and 2 return values respectively when the function is called.
  • The returned functions can be called concurrently.
  • If f is panic when executed, the returned function will also be panic when called, with the same value as f’s panic.

A little bit of expansion on tuples.

If you want to return more return values, mimic constructing one, or encapsulating multiple return values into a single object.

Many years ago, I remember reading an article about some programming language where tuples were hard-coded, two element tuples, three element tuples, four element tuples, etc. I forget which language it was.

So you can imitate it too.

The go-tuple is such an implementation of a go language tuple, supporting up to 9 elements.

 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)

By the way, the above clear , when clearing the map, just leaves the map empty, it does not shrink the map, you can see the example at: https://go.dev/play/p/quVwNvAZAGJ?v=gotip or the related discussion at https://github.com/golang/go/issues/56351

Ref

  • https://colobu.com/2023/05/29/extends-sync-Once/