How atomic module works in Go?

TL;DR

In Go, the atomic package works on pointers, not on copies. It protects a specific memory address but does not act like a higher-level lock.

Why Atomic Is Needed

Adding to a shared variable from many goroutines without atomic means each goroutine reads, increments, and writes back the same old value. The result depends on timing instead of intent.

Example (Race Condition)

package main

import (
	"fmt"
	"sync"
)

func main() {
	var counter int
	var wg sync.WaitGroup

	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			counter++ // not thread-safe!
		}()
	}

	wg.Wait()
	fmt.Println("Final counter:", counter)
}

Expected: 1000 Actual (non-deterministic): something less, due to concurrent writes.

How Atomic Works

With atomic, operations target the memory address directly, and hardware instructions guarantee the update completes without interruption.

Example (Using Atomic)

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func main() {
	var counter int64
	var wg sync.WaitGroup

	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			atomic.AddInt64(&counter, 1) // atomic operation on pointer
		}()
	}

	wg.Wait()
	fmt.Println("Final counter:", counter)
}

Now the output is always 1000 because atomic.AddInt64 updates the shared variable safely.

Be Careful When Using Atomic

atomic protects individual variables, not multi-step logic. When you need to guard a sequence of reads and writes, reach for sync.Mutex.

Appendix

Further reading: Go Concurrency and Atomics by Anton Zhiyanov and the official sync/atomic docs.