We have seen sync. Mutex and sync.RWMutexconcepts in our previous blogs and we’ve also seen various examples associated to them. They all are used to resolve contention amongst the contesting go-routines.
As we’ve seen through all these three episodes, these synchronization primitives are used to guard critical sections and also ensure the reliability of CRUD operations around common data-stores and global variables.
However, for global variables, in particular, there’re very good synchronization primitives made available by the first class package in GoLang. This package is sync/atomic.
Let’s go through a practical scenario. Let’s assume there’re certain IOT devices need to be commissioned in the area where 24 hours surveillance is promised. Each device needs to be tagged with the following format: dd-mm-yyyy:hh-mm-ss-mmm:gid Where dd-mm-yyyy:hh-mm-ss-mmm is the time signature – up through milliseconds – of the time when
the device is commissioned. And gid is a unique number that resembles auto_increment or says sequence column level constraint of a db table. This means each device needs a GID that has to be unique. One way, as we’ve seen in the 1st blog of this series, is to use sync. Mutex, i.e., create a global unsigned integer variable and use a sync. Mutex variable to guard the same. We’ve studied this approach and also done some hands-on coding around the same. However, the exactly same functionality is provided by the first class package sync/atomic of GoLang.
This exactly what the package itself says, “Package atomic provides low-level atomic memory primitives useful for implementing synchronization algorithms.” The package is full of such exported functions that help to achieve exactly what we’ve stated in the above mentioned example.
Let’s have a look what all primitives are provided by sync/atomic package. They’re of following five types of atomic functions for an integer type T, where T is either of int32, int64, uint32, uint64, or uintptr.
func AddT(addr *T, delta T) (new T)
func LoadT(addr *T) (val T)
func StoreT(addr *T, val T)
func SwapT(addr *T, new T) (old T)
func CompareAndSwapT(addr *T, old, new T) (swapped bool)
For instance, AddInt32(), AddUint64(), LoadInt32(), etc..
While AddT are used to update, LoadT are used to read atomically while they’re being updated.
Let’s jump to the code directly to understand how effective these functions are in the context of concurrency.
Example-1: Using AddT
Let’s assume a case where we’re maintaining the number of times a specific handler is invoked. We’ll write a very primitive example to demonstrate the same. This’s a go-routine and resembles an API handler which is executed and an API is invoked from the outside world. We’re kind of attempting to simulate this very situation.