Go Atomics

Similar to C++ Atomics, Go Atomic operations provide safe, lock-free updates to shared variables across multiple goroutines. Use CPU-level hardware instructions instead of heavy OS locks.
Provided by sync/atomic package.

With Atomic Without Atomic
Thread-safe Standard Go types are thread safe Not thread safe
functions to work on data Add(), Load(), Store(), Swap()

Code

Code with Atomic (Correct value) Code without Atomic

package main
import (
	"fmt"
	"sync"
	"sync/atomic"
)
func main() {
	var counter atomic.Uint64   ///////// Atomic variable
	var wg sync.WaitGroup

	// Launch 100 goroutines concurrently
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for j := 0; j < 1000; j++ {
				// Atomically increment the counter
				counter.Add(1)
			}
		}()
	}
	wg.Wait()
	// Safely load and print the atomic value
	fmt.Printf("Expected: %d, Actual: %d\n", 100000, counter.Load())
}

$ go run
Expected: 100000, Actual: 100000
          

package main
import (
	"fmt"
	"sync"
)
func main() {
	var counter int64
	var wg sync.WaitGroup

	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for j := 0; j < 1000; j++ {
				// Race Condition: 
        // Multiple goroutines may read and write simultaneously
				counter++
			}
		}()
	}
	wg.Wait()
	// Will print a random number less than 100000
	fmt.Printf("Expected: %d, Actual: %d\n", 100000, counter)
}

$ go run
Expected: 100000, Actual: 86879
          

How Atomic Works at the CPU Hardware Level?

1st check memory layouts of Process and Thread.
Atomic variables are allocated on common area(DS or HS).
Atomic forces the CPU hardware to line up the requests so they execute one by one at the hardware level.
Go compiler translates that function into special atomic CPU instructions (like LOCK XADD or CMPXCHG on x86 processors)