Synchronized goroutines (part III)

Introduction to mutexes and sync.Once

Suppose there is a functionality in your program which requires some kind of initialization. That bootstrap process is costly so makes sense to defer it to the moment where feature is actually used. This way when feature is not activated CPU cycles won’t be wasted. How it can be done in Go?

package main import "fmt" var capitals map[string]string func bootstrap() {

capitals = make(map[string]string)

capitals["France"] = "Paris"

capitals["Germany"] = "Berlin"

capitals["Japan"] = "Tokyo"

capitals["Brazil"] = "Brasilia"

capitals["China"] = "Beijing"

capitals["USA"] = "Washington"

...

capitals["Poland"] = "Warsaw"

} func getCapitalCity(country string) string {

if capitals == nil {

bootstrap()

}

return capitals[country]

} func main() {

fmt.Println(getCapitalCity("Poland"))

fmt.Println(getCapitalCity("USA"))

fmt.Println(getCapitalCity("Japan"))

}

You can imagine that bootstrap function could be quite expensive if it would handle all countries, other database-like structures, use I/O operations etc. Solution above seems simple and elegant but unfortunately it doesn’t work as it should. The problem is that when bootstrap function is running then nothing stops other goroutine to do the same. It’s desirable though to do heavy computations only once. Additionally right after capitals is initialized but before any key has been set, other goriutines will see it as not nil and will try to get values from empty map.

Go has built-in sync package with lots of goodies inside. To solve our synchronization problem we can use mutex (mutual exclusion):

import (

"fmt"

"sync"

) ... var (

capitals map[string]string

mutex sync.Mutex

) ... func getCapitalCity(country string) string {

mutex.Lock()

if capitals == nil {

bootstrap()

}

mutex.Unlock()

return capitals[country]

}

The issue with running bootstrap process multiple times has been mitigated. If any goroutine is running bootstrap or even is checking if capitals == nil then other goroutines will wait at mutex.Lock() . As soon as Unlock method will be called another goroutine will be “let in”.

But only one goroutine at once can run what is placed between mutex.Lock() and mutex.Unlock() . Consequently if map with capital cities is read by many goroutines then everything will be processed one-by-one at if statement. Basically read access to map (including check if it’s not nil) should be allowed for many activities at once since it’s thread-safe.

Reader / writer mutex can be held at once either by many readers or just single writer (writer is something that modify data):

mutex sync.RWMutex ... func getCapitalCity(country string) string {

mutex.RLock()

if capitals != nil {

country := capitals[country]

mutex.RUnlock()

return country

}

mutex.RUnlock()

mutex.Lock()

if capitals == nil {

bootstrap()

}

mutex.Unlock()

return getCapitalCity(country)

}

Now the code is much more complex. The first part uses read-only lock to allow many readers to access capitals. At the beginning when bootstrap hasn’t been done yet the callers will reach mutex.Lock() and will do necessary initialization. When this is done function can be called for the second time to retrieve desired value. This is done after bootstrap function already returns.

Latest solution is obviously harder to maintain. Fortunately there is a nice built-in method helping with exactly such case…

once sync.Once ... func getCapitalCity(country string) string {

once.Do(bootstrap)

return capital[country]

}

The code above is even simpler that the first concise solution (which didn’t worked). It’s guaranteed that bootstrap will be called exactly once and reading from map will be done only after bootstrap returns.