Introduction
While one of Go's tenets is to share memory through communicating, some issues nevertheless necessitate a shared state. This is sometimes for performance reasons and other times because the resource in issue is shared by nature, such as application settings.
That package implements atomic operations, which are non-interruptible activities.
Atomic Values in Go
Atomic Package
Atomic Package contains low-level atomic memory primitives that can be used to create synchronization algorithms.
These functions must be utilized with extreme caution. Except for particular, low-level applications, synchronization is best accomplished using channels or the sync package's features. Don't communicate by sharing memory; instead, share memory by communicating.
Example
The SwapT functions implement the swap operation, which is the atomic equivalent of:
old_value = *addr
*addr = new_value
return old_value
Atomic Variables
Atomic Variables are used to control state in the Go programming language. The "sync/atomic" package must be used to utilize these variables. Furthermore, it prevents race circumstances, which allow two or more Goroutines to access the same sources. Atomic counters are provided for a variety of goroutines. Communication across channels is the primary technique of controlling states in the Go language.
Example
// Golang program to illustrate the usage of
// atomic variable
package main
import (
"fmt"
"math/rand"
"sync"
"sync/atomic"
"time"
)
// Using sync.WaitGroup to
// wait for a collection of
// goroutines to finish
var waittime sync.WaitGroup
// Declaration of atomic variable
var atomic_var int32
//Increment function
func inc(S string) {
for i := 1; i < 7; i++ {
// Calling sleep and rand.Intn method
time.Sleep(time.Duration((rand.Intn(5))) * time.Millisecond)
// Calling AddInt32 method
atomic.AddInt32(&atomic_var, 1)
fmt.Println(S, i, "count ->", atomic_var)
}
// Wait completed
waittime.Done()
}
func main() {
// Calling Add method w.r.t waittime variable
waittime.Add(2)
// Calling inc method with
go inc("abc: ")
go inc("pqr: ")
// Calling wait method
waittime.Wait()
// Prints the value of last count
fmt.Println("The value of last count is :", atomic_var)
}
Output
pqr: 1 count -> 1
abc: 1 count -> 2
pqr: 2 count -> 3
pqr: 3 count -> 4
abc: 2 count -> 5
abc: 3 count -> 6
abc: 4 count -> 7
pqr: 4 count -> 8
pqr: 5 count -> 10
abc: 5 count -> 9
abc: 6 count -> 11
pqr: 6 count -> 12
The value of last count is : 12
The atomic variable in the above example is of type int32. In this case, the rand.Intn() function is used to output a random number until the loop is terminated. Following that, the AddInt32 function is invoked, which adds the atomic variable to another int32 number and returns the result in the count.
Lock-free values
The canonical technique to safely share state in Go is to use mutexes.
The atomic function in the standard library provides an alternate method for achieving thread safety for arbitrary types through the atomic.Value type.
Example
package main
import (
"fmt"
"sync/atomic"
)
type User struct {
FName string
LName string
}
var GlobalUser atomic.Value
func main() {
user := User{"Sample", "User"}
GlobalUser.Store(user) // atomic/thread-safe
data := GlobalUser.Load().(User) // atomic/thread-safe
fmt.Printf("%+v", data)
}
Output
{FName:Sample LName:User}
atomic.Value appears to have a very straightforward and user-friendly UI. It is not just thread-safe, but also lock-free.
Atomic operations
If we use channels on a single shared variable to perform an operation like increment or decrement, the goroutines will not be synchronized and will provide incorrect results. On the other hand, atomic operations are implemented at the hardware level. That is, if we establish a shared atomic variable and change its value using many goroutines, the value will be updated appropriately.
Example
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func count(v *uint32, wg *sync.WaitGroup) {
for i := 0; i < 3000; i++ {
atomic.AddUint32(v, 1)
}
wg.Done()
}
func main() {
var v uint32 = 42
var wg sync.WaitGroup
wg.Add(2)
go count(&v, &wg)
go count(&v, &wg)
wg.Wait()
fmt.Println(v)
}
Output
6095
Because the atomic actions are synchronized, it will yield the correct output each time we execute it.