Example 1: Golang Atomic LoadInt32() Function
Begin with the atomic.LoadInt32() function example here. The atomic.LoadInt32() function is part of the sync/atomic package and is used to atomically load (read) the value of a 32-bit integer variable. Here, we can get more about the function via the following example:
import "fmt"
import "sync/atomic"
func main() {
var n1 int32 = 123
var n2 int32 = 321
loadNum1 := atomic.LoadInt32(&n1)
loadNum2 := atomic.LoadInt32(&n2)
fmt.Println(loadNum1)
fmt.Println(loadNum2)
}
In the code, we define the sync/atomic package which provides functionality for atomic memory operations. Here, we import this package to gain an access to its functions. Moving on, in the main() function, we declare two int32 variables – “n1” and “n2” – with the values, respectively. These variables hold the integers that we intend to load atomically.
After that, “loadNum1” and “loadNum2” are declared to hold the loaded values of “n1” and “n2”, respectively. The results of the atomic loads are stored in these variables. Then, we employ the atomic.LoadInt32 function to atomically load the values of “n1” and “n2”. It takes the memory address of the variables as a parameter which is indicated by the & operator to directly access the underlying memory location.
The retrieved output indicates that the values of “n1” and “n2” are successfully loaded into “loadNum1” and “loadNum2”:
Example 2: Golang Atomic LoadInt64() Function
Subsequently, the atomic.LoadInt64() function is also a part of the sync/atomic package and is used to atomically load (read) the value of a 64-bit integer variable. The function can be clearer from the given code.
import "fmt"
import "sync/atomic"
func main() {
var int1 int64 = 93882473249
var int2 int64 = 18470101555
Val1 := atomic.LoadInt64(&int1)
Val2 := atomic.LoadInt64(&int2)
fmt.Println(Val1)
fmt.Println(Val2)
}
In the code, we define two int64 variables – “int1” and “int2” – which are initialized with the long integer values, respectively. The atomic.LoadInt64() function is then used to atomically load the values of “int1” and “int2”. Similar to the previous example, it takes the memory address of the variable as its argument and returns the current value that is stored at that memory location. Here, it loads the values of “int1” and “int2” into the “Val1” and “Val2” variables, respectively. Subsequently, the fmt.Println() function is used to print the values of “Val1” and “Val2”.
The output shows the values of “Val1” and “Val2” that were loaded atomically from “int1” and “int2”, respectively:
Example 3: Golang Atomic StoreInt64() Function
In addition, to store an int64 or int32 atomically, we can use the atomic.StoreInt64() function or atomic.StoreInt32() function. The example that is given here uses the atomic.StoreInt64() function.
import (
"fmt"
"sync/atomic"
)
func main() {
var (
a int64
b int64
)
atomic.StoreInt64(&a, 4555454555)
atomic.StoreInt64(&b, 13388)
fmt.Println(atomic.LoadInt64(&a))
fmt.Println(atomic.LoadInt64(&b))
}
In the code, we first create two variables – “a” and “b” – which are declared with the type “int64”. These variables store the 64-bit signed integer values. After that, we use the atomic.StoreInt64() function to atomically store a value into an “int64” variable. The variable address that has to be modified is provided as the first parameter by the “&” operator. A value must be stored, which is the second argument. After storing the variables in the atomic.StoreInt(), we load the values of variables “a” and “b” into the atomic.LoadInt64() function, respectively.
The displayed output is the loaded variable values using the atomic operation:
Example 4: Golang Atomic CompareAndSwapInt64() Function
Certainly, the atomic package in Go provides the CompareAndSwapInt64() function to perform an atomic compare-and-swap operation on an “int64” value. Consider the example of the atomic CompareAndSwapInt() function.
import (
"fmt"
"sync/atomic"
)
func main() {
var c int64 = 0
atomic.AddInt64(&c, 1)
awaited := int64(0)
new := int64(1)
atomic.CompareAndSwapInt64(&c, awaited, new)
val := atomic.LoadInt64(&c)
fmt.Println(val)
}
In the code, we begin with the declaration of the “c” variable of type “int64” and initialize it to “0”. We then use the atomic.AddInt64() function to atomically increment the value of “c” by “1”. This function ensures that the increment operation is executed atomically to prevent the concurrent access issues.
After that, we define the “awaited” and “new” variables, both of type “int64”. Here, the “awaited” variable is set to 0, and the “new” variable is set to 1. We then employ the atomic.CompareAndSwapInt64() function to compare the value of “c” with “awaited” (0) and swap it with “new” (1) if the values match. Here, the function returns a Boolean which indicates whether the swap is successful. In this case, if “c” is 0, it will be swapped with 1. Thereafter, with the use of the atomic.LoadInt64() function, we atomically load the value of “c” into the “val” variable
The output generats the value of “c” as 1 which indicates that the swap is successfully done:
Example 5: Golang Atomic AddUint32() Function
Furthermore, we have the AddUint32() function for atomic addition operations on “uint32” variables. This function allows us to add a value to a “uint32” variable atomically. Here’s the program where we use the AddUnit32() function to add a “unit32” variable value:
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var x uint32
var wait sync.WaitGroup
for i := 0; i < 20; i += 1 {
wait.Add(1)
go func() {
atomic.AddUint32(&x, 1)
wait.Done()
}()
}
wait.Wait()
fmt.Println("Atomic Variable Value:", x)
}
In the code, we have an “x” variable of type “uint32”. This variable is incremented atomically. Here, we use a “for” loop to spawn “20” goroutines that concurrently increment the value of “x”. Inside the goroutine, we use the atomic.AddUint32() function to atomically increment the value of “x” by 1. This function ensures that the increment operation is executed atomically to prevent the race conditions.
Next, we call the “wait.Add(1)” to add a counter to the WaitGroup which indicates that a goroutine is about to start. We use an anonymous function for the goroutine and call the wait.Done() when the goroutine completes the decrement of the WaitGroup counter. Lastly, we call the “wait.Wait()” function to wait until all the spawned goroutines have finished executing. This ensures that the program doesn't exit prematurely.
The following output represents the 20 goroutines that concurrently increment the “x” variable using the atomic operations:
Conclusion
The atomic operation is explored in this article with running examples. These examples include the atomic load and store operation, compare and swap operation, and add unit operation. Understanding the atomic operations and applying them appropriately can significantly improve the reliability and correctness of the concurrent Go applications.