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!")
}
As your code shows, there's no way to upgrade from a read to a write lock, you have to release the read lock and then take the write lock.
I think you're close, the use of RWMutex
itself looks fine, however i believe you have a potential issue with calling wg.Add(1)
after releasing the lock. There's a chance that one of the other goroutines will read the WaitGroup out of the map and call Wait() before the call to Add() is executed, which according to the WaitGroup docs is an issue. The docs say Note that calls with a positive delta that occur when the counter is zero must happen before a Wait.
You can easily fix this by moving the wg.Add(1)
call before the Unlock()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With