An atomic operation is a low-level, indivisible operation that is guaranteed to execute without interruption. Atomic operations ensure that a value is read and written in a single, uninterrupted operation, which is crucial when dealing with shared variables in concurrent programming. This prevents race conditions and ensures the integrity of data.
Go provides the sync/atomic
package to perform atomic operations on variables. The most common types of atomic operations include:
- atomic.AddInt32, atomic.AddInt64: Atomically adds a value to an integer.
- atomic.CompareAndSwapInt32, atomic.CompareAndSwapInt64: Atomically compares and swaps a value.
- atomic.LoadInt32, atomic.LoadInt64: Atomically loads a value.
- atomic.StoreInt32, atomic.StoreInt64: Atomically stores a value.
In Go, atomic operations are typically used on integer types (e.g., int32
, int64
) and pointers. These types ensure that operations are performed atomically and without race conditions.
import "sync/atomic"
Here are some examples of common atomic operations:
atomic.AddInt32(&counter, 1) // Atomically adds 1 to the counter
success := atomic.CompareAndSwapInt32(&counter, oldValue, newValue)
value := atomic.LoadInt32(&counter) // Atomically loads the value of the counter
atomic.StoreInt32(&counter, 100) // Atomically sets the counter to 100
package main
import (
"fmt"
"sync"
"sync/atomic"
)
var counter int32
func increment() {
atomic.AddInt32(&counter, 1) // Atomically increment counter
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}
wg.Wait()
fmt.Println("Final counter:", counter) // Output: Final counter: 1000
}
- Atomicity: Atomic operations guarantee that a value is modified in one indivisible operation. This ensures that the value is either fully updated or not updated at all, without interference from other goroutines.
- Compare and Swap (CAS): This is a common atomic operation where you compare the current value of a variable to an expected value, and if they match, you swap it with a new value. This operation is widely used for implementing lock-free data structures and algorithms.
- Use atomic operations for simple types: Use atomic operations on
int32
,int64
, and pointers to avoid the overhead of locks, especially when you need to ensure that the value is updated atomically. - Avoid locking when using atomic operations: If the problem can be solved with atomic operations, prefer them over locks to reduce contention and improve performance.
- Use
sync/atomic
with caution: Atomic operations can be tricky to use correctly, especially in complex scenarios. Ensure that the operation you're performing is genuinely atomic and that the state you're managing can be updated in a thread-safe way.