The ‘atomic‘ package in Go provides low-level atomic operations for memory synchronization. It helps ensure that concurrent operations on shared variables are handled safely and efficiently, without the use of mutexes or other higher-level synchronization mechanisms.
To use the ‘atomic‘ package, you need to first import it:
import "sync/atomic"
Here, we will discuss some common atomic operations provided by the ‘atomic‘ package and present examples demonstrating their use.
1. **AddInt32/AddInt64**: These functions atomically add an integer value (32-bit or 64-bit) to the given address and return the new value.
var count int32
func increment() {
newVal := atomic.AddInt32(&count, 1)
fmt.Println("New value:", newVal)
}
2. **CompareAndSwapInt32/CompareAndSwapInt64**: These functions (for 32-bit and 64-bit integers) atomically compare a variable with a given value—if they match, it sets the variable to a new value and returns true. Otherwise, it returns false.
Consider a simple example of a bank transaction where we need to update the balance atomically:
var balance int64 = 500
func updateBalance(delta int64) {
for {
currentBalance := atomic.LoadInt64(&balance) // Read the current balance atomically
newBalance := currentBalance + delta
if atomic.CompareAndSwapInt64(&balance, currentBalance, newBalance) {
fmt.Printf("Balance updated from %d to %dn", currentBalance, newBalance)
break
}
}
}
3. **LoadInt32/LoadInt64 and StoreInt32/StoreInt64**: These functions are used to atomically load and store 32-bit or 64-bit integer values.
var count int32
func incrementAndLoad() {
atomic.AddInt32(&count, 1)
newVal := atomic.LoadInt32(&count)
fmt.Println("New value:", newVal)
}
func storeAndLoad(val int32) {
atomic.StoreInt32(&count, val)
newVal := atomic.LoadInt32(&count)
fmt.Printf("Stored value: %d, Loaded value: %dn", val, newVal)
}
Here is a complete example demonstrating the use of atomic operations to implement synchronization for a simple counter:
package main
import (
"sync"
"sync/atomic"
"time"
)
var count int64
func main() {
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
incrementCounter(&count)
wg.Done()
}()
}
wg.Wait()
finalCounter := atomic.LoadInt64(&count)
fmt.Printf("Final counter value: %dn", finalCounter)
}
func incrementCounter(counter *int64) {
for i := 0; i < 1000; i++ {
atomic.AddInt64(counter, 1)
time.Sleep(time.Duration(1 * time.Millisecond))
}
}
In this example, the ‘atomic.AddInt64‘ function is used to increment the ‘count‘ variable atomically, ensuring that multiple go routines will not cause race conditions. Finally, the ‘atomic.LoadInt64‘ function is used to obtain the final value of the counter safely.