I'm trying to implement the pattern of access to a map that allows multiple readers and only one writer and where the key is only written to the map once.
I want to make sure that the key, if it exists, is only ever placed into the map one time. And then once the key is added, the readers obtain the value from the map.
It think this is correct, although the main thread could exit before all the goroutines finish, hence I don't always see 10 Exit's printed. Is this the correct pattern of implementation?
package main
import (
"fmt"
"sync"
"time"
)
var downloads map[string]*sync.WaitGroup
var downloadsLock sync.RWMutex
func concurrentAccessTest() {
// Acquire the read lock to check if the items is already there.
downloadsLock.RLock()
if wg, ok := downloads["item"]; ok {
// Item exists
downloadsLock.RUnlock()
wg.Wait()
} else {
downloadsLock.RUnlock()
fmt.Println("Upgrade Lock")
// Item does not exist we need to add it to the map
downloadsLock.Lock()
fmt.Println("Writer lock obtained")
// Check another thread hasn't gone down this path and added the item to the map
if wg, ok := downloads["item"]; ok {
// Item exists, no need to add it, unlock and wait
downloadsLock.Unlock()
wg.Wait()
fmt.Println("Ok, now we can proceed")
} else {
// We are first in. Add and unlock
wg = &sync.WaitGroup{}
downloads["item"] = wg
downloadsLock.Unlock()
wg.Add(1)
fmt.Println("Do some time consuming stuff!")
time.Sleep(5 * time.Second)
wg.Done()
}
}
fmt.Println("Exit")
}
func main() {
downloads = make(map[string]*sync.WaitGroup)
// Add to the map
for i := 0; i < 10; i++ {
go concurrentAccessTest()
}
concurrentAccessTest()
// Wait for all threads to exit
fmt.Println("Done!")
}