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

# Go High-Performance Programming EP11: lock-free practice
- URL: https://huizhou92.com/go-high-performance-programming-ep11-lock-free-practice/
- Published: 2024-11-15T10:17:55.000Z
- Updated: 2026-09-06T11:40:18.000Z
- Author: huizhou92
- Tags: tech, go, free-lock, #lang-en

## Introduction

The previous article discussed two lock-free programming strategies: eliminating shared data and avoiding concurrent access to the same data through careful design. While achieving lock-free programming is ambitious, it could be more practical. Generally, when discussing lock-free techniques, the aim is to avoid locks, and using Compare-And-Swap (CAS) is a good alternative. If CAS is insufficient, we can minimize the granularity of `Mutex` or replace it with `RWMutex`.

### Advantages of Lock-Free Programming

- Reduces thread blocking and waiting time
- It avoids thread priority inversion.
- Improves concurrency performance.
- Eliminates issues like race conditions, deadlocks, and starvation.
- Simplifies and clarifies code.

This article will explore and implement several lock-free programming techniques in Go.

### Channel

The Go language advocates for the principle of sharing memory through communication, as stated in its [official blog](https://go.dev/blog/codelab-share?ref=huizhou92.com):

> *Do not communicate by sharing memory; instead, share memory by communicating.*

Channels enable lock-free programming because:

1. Channel transfer ownership of data.
2. The channel uses internal locks for synchronization.

For example:

```go
type Resource string

func Poller(in, out chan *Resource) {
    for r := range in {
        // Process the URL
        // Send the processed Resource to the output channel
        out <- r
    }
}

```

Data flows between `in` and `out` channels without multiple goroutines simultaneously operating on the same data, achieving a lock-free design.

---

## CAS (Compare-And-Swap)

Modern CPUs support atomic CAS operations, allowing atomic data exchange in multithreaded environments. CAS helps avoid data inconsistencies caused by unpredictable execution orders and interruptions.  
more about cas.

### Lock-Free Stack Using CAS

A stack can typically be implemented with `slice` and `RWMutex` for concurrency safety. Alternatively, CAS can be used to implement a lock-free stack. Below is an example:  
[Lock-Free Stack Implementation](https://gist.github.com/hxzhouh/432ec000d4d9f59bd75e5fdc535822ad?ref=huizhou92.com)

### Benchmarking

A simple benchmark test shows the lock-free stack outperforms the `slice` \+ `RWMutex` implementation by approximately 20%. The performance gains are even more significant in high-concurrency scenarios.

```go
func BenchmarkConcurrentPushLockFree(b *testing.B) {
    stack := NewStackByLockFree()
    for i := 0; i < b.N; i++ {
       stack.Push(i)
    }
}

func BenchmarkConcurrentPushSlice(b *testing.B) {
    stack := NewStackBySlice()
    for i := 0; i < b.N; i++ {
       stack.Push(i)
    }
}

```

```shell
➜  lock-free git:(main) ✗ go test --bench=.

BenchmarkConcurrentPushLockFree-10      22657522                49.66 ns/op
BenchmarkConcurrentPushSlice-10         29135671                59.04 ns/op

```

---

## Struct Copy: Trading Space for Time

Struct Copy is a technique that avoids data contention by duplicating shared resources. Each goroutine operates on its own copy, eliminating the need for locks.

### Example

[Struct Copy Implementation](https://gist.github.com/hxzhouh/903bc495c956a887b3c97f822ce79ab3?ref=huizhou92.com)

Each `goroutine` owns a unique instance by wrapping shared resources with a BufferWrapper, avoiding data races, and using `sync.Pool` further optimizes memory allocation.

### Benchmarking

```go
func BenchmarkSingleBuffer_print(b *testing.B) {
    for i := 0; i < b.N; i++ {
       run_buff_bench(singleBuff)
    }
}

func BenchmarkBufferWrapper_print(b *testing.B) {
    bufferWrapper := NewBufferWrapper()
    for i := 0; i < b.N; i++ {
       run_buff_bench(bufferWrapper)
    }
}

func run_buff_bench(buff printId) {
    wg := sync.WaitGroup{}
    f := func(id int) {
       defer wg.Done()
       for j := 0; j < 10000; j++ {
          buff.print(id)
       }
    }
    for j := 0; j < 100; j++ {
       wg.Add(1)
       go f(j)
    }
    wg.Wait()
}

```

```shell
BenchmarkSingleBuffer_print-10                 4         261640427 ns/op
BenchmarkBufferWrapper_print-10               67          18320110 ns/op

```

A similar technique is used in [fastjson's](https://github.com/valyala/fastjson?ref=huizhou92.com) [Parser](https://github.com/valyala/fastjson/blob/93f67d942133e9d219dbbae7a541433f8bbbe7c4/pool.go?ref=huizhou92.com#L15).

## Conclusion

This article explored three practical techniques for lock-free programming in Go, with performance tests validating their effectiveness. While fully lock-free programming is challenging, proper design and technique selection can significantly reduce lock usage and enhance concurrency performance.