Atomic Operations in Golang – atomic package

Atomic operations are those which are implemented at the hardware level. Go has the atomic package which helps to achieve synchronization when doing concurrency. In this post, we will see what the atomic package has to offer.

What is an atomic operation?

If we do an operation like increment or decrement using channels on a single shared variable, the goroutines will not be synchronized and will produce erroneous output. The atomic operations, on the other hand, are implemented at the hardware level. That means when we create a shared atomic variable and use multiple goroutines to update its value it will be updated correctly.

Importing Golang atomic package

To use the atomic functions we need to import the sync/atomic package.

import "sync/atomic"

Atomic functions in Golang

The package contains load, store and the addition operation for the int32, int 64, uint32, uint64, etc. Since only ints are such primitive that can be correctly synchronized using atomics.

Let’s see an example of what can happen if we don’t use atomic.

package main

import (
	"fmt"
	"sync"
	// "sync/atomic"
)

func f(v *int, wg *sync.WaitGroup) {
	for i := 0; i < 3000; i++ {
		*v++
	}
	wg.Done()
}

func main() {
	var v int = 42
	var wg sync.WaitGroup
	wg.Add(2)
	go f(&v, &wg)
	go f(&v, &wg)
	wg.Wait()

	fmt.Println(v)
}
Go Non Atomic
Go Non-Atomic operation

As can be seen each time the program is run it will produce the wrong output. It happens due to the fact the goroutines accessing and changing the value is not synchronized. So, the changed values are arbitrary and will result in the wrong number of operations.

Now, we convert the code above to synchronized code using atomic.

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func f(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 f(&v, &wg)
	go f(&v, &wg)
	wg.Wait()

	fmt.Println(v)
}

Now, each time we run it, it will produce the correct output. Since the atomic operations are synchronized it will return the correct output no matter what.

Go Atomic Operation
Go Atomic Operation

Use of atomic operations in Golang

Atomic operations are used when we need to have a shared variable between different goroutines which will be updated by them. If the updating operation is not synchronized then it will create a problem that we saw.

Atomic operations solve that problem by synchronizing access to the shared variable and making the output correct.