Skip to main content

Concurrency State Management

It is essential to properly manage state of an applicaton that utilizes concurrency. There are various ways to do them in Go.

Using sync/atomic

The sync/atomic package provides low-level atomic memory primitives useful for implementing an atomic counter.

var totalDone uint64

var wg sync.WaitGroup

for i := 0; i < 50; i++ {
wg.Add(1)
go func() {
atomic.AddUint64(&totalDone, 1) //increment by 1
wg.Done()
}()
}

wg.Wait()

fmt.Println("Total done:", totalDone)

This should print out Total done: 50.

Using sync.Mutex

Mutexes can be allowed to manage more complex state and provide safe access to goroutines.

//create a container struct
type container struct {
//mutex must be added to the struct
//to enable locking and unlocking
mu sync.Mutex
counters map[string]int
}

//implement a method to increment a counter
func (c *container) incrementCounter(key string) {
c.mu.Lock() //lock the mutex to prevent other goroutines from accessing the map as it is being modified
defer c.mu.Unlock() //unlock the mutex when the function returns
c.counters[key]++
}

func main(){
c := container{
//mutex requires no initialization
counters: make(map[string]int),
}

var wg sync.WaitGroup

incCounters := func(name string, n int) {
for i := 0; i < n; i++ {
//increment the particular counter
c.incrementCounter(name)
}
}

wg.Add(2)

go incCounters("foo", 50)
go incCounters("bar", 50)

wg.Wait()
fmt.Println(c.counters)
}

The above code should print out map[bar:50 foo:50]. If the mutex was not used, the map would be in an inconsistent state, and the counters would not be accurate.