Table of contents
1.
Introduction
2.
Atomic Values in Go
2.1.
Atomic Package
2.1.1.
Example
2.2.
Atomic Variables
2.2.1.
Example
2.3.
Lock-free values
2.3.1.
Example
2.4.
Atomic operations
2.4.1.
Example
3.
FAQs
4.
Key Takeaways
Last Updated: Mar 27, 2024

Go Atomic Values

Author Sanjana Yadav
0 upvote
Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

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.

FAQs

  1. What is atomic value Golang?
    Atomic Variables are used to govern the state of the system. To utilize these variables, the "sync/atomic" package must be used. Furthermore, it prevents race situations, which occur when two or more Goroutines access the same sources. For a variety of goroutines, atomic counters are provided.
     
  2. Is bool atomic Golang?
    Bool is an atomic type-safe wrapper for bool values.
     
  3. What is store Golang?
    In the Go programming language, the Store() function is used to set the value of the Value to x. (i.e, interface).

Key Takeaways

In this article, we have extensively discussed several atomic values in Go. With the help of several examples, we understood the scenarios when these values are used.

We hope that this blog has helped you enhance your knowledge on Go channel and if you would like to learn more about Go, check out our articles Golang Archives. Also check out our article on Go Defer. Do upvote our blog to help other ninjas grow. Happy Coding!

Live masterclass