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.