> ## Content Index
> Fetch the complete content index at: https://huizhou92.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Overview of Changes in Go’s Concurrency Library in 2023
- URL: https://huizhou92.com/overview-of-changes-in-gos-concurrency-library-in-2023/
- Published: 2026-09-06T11:38:55.000Z
- Updated: 2026-09-06T11:38:55.000Z
- Description: In 2023, there have been some changes to Go’s concurrency library, and this article provides an overview of these changes. Minor details such as typos and documentation changes will not be covered.
- Author: huizhou92
- Tags: tech, concurrency, golang, #lang-en

In 2023, there have been some changes to Go’s concurrency library, and this article provides an overview of these changes. Minor details such as typos and documentation changes will not be covered.

> This article is first published in the medium MPP plan. If you are a medium user, please follow me in [medium](https://medium.huizhou92.com/?ref=huizhou92.com). Thank you very much.

## sync.Map

In Go 1.21.0, three functions related to `Once` were added to the `sync` package to facilitate the usage of `Once`:

```go
func OnceFunc(f func()) func()
func OnceValue[T any](f func() T) func() T
func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2)

```

The functionality of these three functions is as follows:

- `OnceFunc`: Returns a function `g` that, when called multiple times, will only execute `f` once. If `f` panics during execution, subsequent calls to `g` will not execute `f`, but each call will still panic.
- `OnceValue`: Returns a function `g` that, when called multiple times, will only execute `f` once. The return type of `g` is `T`, which is an additional return value compared to the previous function. The panic behavior is the same as `OnceFunc`.
- `OnceValues`: Returns a function `g` that, when called multiple times, will only execute `f` once. The return type of `g` is `(T1, T2)`, which is an additional return value compared to the previous function. The panic behavior is the same as `OnceFunc`.

In theory, you can add more functions and return more values. However, since Go does not have a tuple type, the return values of function `g` cannot be simplified to a tuple type. In any case, Go 1.21.0 only added these three functions.

What are the benefits of these functions? Previously, when using `sync.Once`, such as initializing a thread pool, we needed to define a variable for the thread pool and call `sync.Once.Do` every time we accessed the thread pool variable:

```go
func TestOnce(t *testing.T) {
   var pool any
   var once sync.Once
   var initFn = func() {
     // initialize pool
     pool = 1
   }
   for i := 0; i < 10; i++ {
     once.Do(initFn)
     t.Log(pool)
   }
 }

```

With `OnceValue`, the code can be simplified:

```go
func TestOnceValue(t *testing.T) {
   var initPool = func() any {
     return 1
   }
   var poolGenerator = sync.OnceValue(initPool)
   for i := 0; i < 10; i++ {
     t.Log(poolGenerator())
   }
 }

```

The code is slightly simplified, and you only need to call the returned function `g` to obtain the singleton.

In summary, these three functions are just encapsulations of `sync.Once` to make it more convenient to use.

## Understanding copyChecker

We know that `sync.Cond` has two fields, `noCopy` and `checker`. `noCopy` can be statically checked using the `go vet` tool, but `checker` is checked at runtime:

```go
type Cond struct {
   noCopy noCopy
   // L is held while observing or changing the condition
   L Locker
   notify  notifyList
   checker copyChecker
 }

```

Previously, the conditions for `copyChecker` were as follows, although it is just three simple lines, it is not easy to understand:

```go
func (c *copyChecker) check() {
   if uintptr(*c) != uintptr(unsafe.Pointer(c)) &&
     !atomic.CompareAndSwapUintptr((*uintptr)(c), 0, uintptr(unsafe.Pointer(c))) &&
     uintptr(*c) != uintptr(unsafe.Pointer(c)) {
     panic("sync.Cond is copied")
   }
 }

```

Now, with added comments, the meaning of these three lines is explained:

```go
func (c *copyChecker) check() {
 ​
   // Check if c has been copied in three steps:
 ​
   // 1. The first comparison is the fast-path. If c has been initialized and not copied, this will return immediately. Otherwise, c is either not initialized, or has been copied.
 ​
   // 2. Ensure c is initialized. If the CAS succeeds, we're done. If it fails, c was either initialized concurrently and we simply lost the race, or c has been copied.
 ​
   // 3. Do step 1 again. Now that c is definitely initialized, if this fails, c was copied.
   if uintptr(*c) != uintptr(unsafe.Pointer(c)) &&
     !atomic.CompareAndSwapUintptr((*uintptr)(c), 0, uintptr(unsafe.Pointer(c))) &&
     uintptr(*c) != uintptr(unsafe.Pointer(c)) {
     panic("sync.Cond is copied")
   }
 }

```

The main logic is as follows:

- The first step is a fast check, directly comparing the pointer of `c` and the pointer of `c` itself. If they are not equal, it means that `c` has been copied. This is the fastest check path.
- The second step ensures that `c` is initialized. It initializes `c` using CAS (CompareAndSwap). If the CAS succeeds, we're done. If it fails, it means that `c` was either initialized concurrently and we simply lost the race, or `c` has been copied.
- The third step repeats the first step’s check. Since we know that `c` is definitely initialized at this point, if the check fails, it means that `c` was copied.

The entire logic uses CAS combined with two pointer checks to ensure the correctness of the judgment.

In summary, the first step is a performance optimization. The second step uses CAS to ensure initialization. The third step rechecks to ensure the judgment.

## Optimization in sync.Map

Previously, the implementation of the `Range` function in `sync.Map` was as follows:

```go
func (m *Map) Range(f func(key, value any) bool) {
     ...
     if read.amended {
       read = readOnly{m: m.dirty}
       m.read.Store(&read)
       m.dirty = nil
       m.misses = 0
   }
     ...
 }

```

There was a line of code: `m.read.Store(&read)`, which caused `read` to escape to the heap. To avoid the escape of `read`, a small trick was employed by introducing a new variable:

```go
func (m *Map) Range(f func(key, value any) bool) {
     ...
   if read.amended {
     read = readOnly{m: m.dirty}
     copyRead := read
     m.read.Store(&Read)
     m.dirty = nil
     m.misses = 0
   }
     ...
 }

```

[Issue #62404](https://github.com/golang/go/issues/62404?ref=huizhou92.com) analyzed this problem.

## Replacing Done in sync.Once Implementation with atomic.Uint32

Previously, the implementation of `sync.Once` was as follows:

```go
type Once struct {
	done uint32
	m    Mutex
}

```

The `done` field was of type `uint32` to indicate whether `Once` has been executed. The reason for using `uint32` instead of `bool` is that `uint32` can be used with atomic operations from the `atomic` package, while `bool` cannot.

Now, the implementation of `sync.Once` is as follows:

```go
type Once struct {
	done atomic.Uint32
	m    Mutex
}

```

Since Go 1.19, the standard library has provided atomic wrappers for basic types, and a large amount of code in the Go standard library has been replaced with `atomic.XXX` types.

In my opinion, this modification may result in a performance decrease compared to the previous implementation in certain cases. I will write an article specifically to explore this.

Besides `sync.Once`, there are other types that have been replaced with `atomic.XXX` types in their usage. Is it necessary to replace them?

## Optimization in Initial Implementation of sync.OnceFunc

The initial implementation of `sync.OnceFunc` was as follows:

```go
func OnceFunc(f func()) func() {
 var (
  once  Once
  valid bool
  p     any
 )

 g := func() {
  defer func() {
   p = recover()
   if !valid {
    panic(p)
   }
  }()
  f()
  valid = true
 }

 return func() {
  once.Do(g)
  if !valid {
   panic(p)
  }
 }
}

```

If you look closely at this code, you will notice that the function `f` passed to `OnceFunc/OnceValue/OnceValues` remains alive even after it has been executed once, as long as the returned function `g` is not garbage collected. This is unnecessary because `f` only needs to be executed once and can be garbage collected afterwards. Therefore, an optimization can be made to set `f` to `nil` after it is executed, allowing it to be garbage collected.

```go
func OnceFunc(f func()) func() {
 var (
  once  Once
  valid bool
  p     any
 )

 // Construct the inner closure just once to reduce costs on the fast path.
 g := func() {
  defer func() {
   p = recover()
   if !valid {
    // Re-panic immediately so on the first call the user gets a
    // complete stack trace into f.
    panic(p)
   }
  }()
  f()
  f = nil      // Do not keep f alive after invoking it.
  valid = true // Set only if f does not panic.
 }

 return func() {
  once.Do(g)
  if !valid {
   panic(p)
  }
 }
}

```

## Context

As we know, in Go 1.20, a new method `WithCancelCause` was added ( `func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc)`), which allows us to pass the cancellation cause to the `Context` generated by `WithCancelCause`. This allows us to retrieve the cancellation cause using the `context.Cause` method.

```go
ctx, cancel := context.WithCancelCause(parent)
cancel(myError)
ctx.Err() // returns context.Canceled
context.Cause(ctx) // returns myError

```

Of course, this implementation is only halfway done, as timeout-related `Context` also needs this functionality. Therefore, in Go 1.21.0, two additional functions were added:

```go
func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc)
func WithTimeoutCause(parent Context, timeout time.Duration, cause error) (Context, CancelFunc)

```

These two functions, unlike `WithCancelCause`, directly pass the cause as a parameter instead of using the returned cancel function.

Go 1.21.0 also introduced a function `AfterFunc`, which is similar to `time.AfterFunc`, but it returns a `Context` that is automatically canceled after the timeout. The implementation of this function is as follows:

```go
func AfterFunc(ctx Context, f func()) (stop func() bool)

```

The specified `Context` triggers the invocation of `f` immediately when done (either due to timeout or cancellation). The returned `stop` function is used to stop the invocation of `f`. If `stop` is called and returns `true`, `f` will not be invoked.

This is a helper function, but it may be difficult to understand, and it is unlikely to be widely used.

Other minor performance optimizations, such as replacing `type emptyCtx int` with `type emptyCtx struct{}`, are not mentioned here.

An additional function `func WithoutCancel(parent Context) Context` was added, which creates a `Context` that is not affected when the parent is canceled.

## golang.org/x/sync No Significant Changes in Sync

`errgroup` now supports setting the cause using `withCancelCause`. `singleflight` added an `Unwrap` method to `panicError`.