> ## 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-2/
- Published: 2024-01-19T12:05:20.000Z
- Updated: 2024-01-19T12:05:20.000Z
- Description: Hello everyone, I’m hxzhouh. In 2023, there have been some changes to Go’s concurrency library, and this article provides an overview of…
- Author: huizhou92
- Tags: #Migrated-1788833207488, #Import 2026-09-08 02:07

> Hello everyone, I’m hxzhouh. 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.

### 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:

```bash
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`.

---

If you found my article enjoyable, feel free to follow me and give it a 👏. Your support would be greatly appreciated.

[文章索引](/article-index/)