Я немного озадачен блокировкой / разблокировкой мьютекса больше раз за другим.Я использую RWMutex , и все программы будут иметь одинаковый мьютекс.
Этот код по-прежнему защищен от гонки при частом использовании мьютексов?
func (r *Redis) RedisDb(dbId DatabaseId) *RedisDb {
r.Mu().RLock()
size := len(r.redisDbs) // A
r.Mu().RUnlock()
if size >= int(dbId) { // B
r.Mu().RLock()
db := r.redisDbs[dbId] // C
r.Mu().RUnlock()
if db != nil { // D
return db
}
}
// E create db...
}
Пример ситуации, о которой я думаю, может произойти:
- gorountine1 и goroutine2 выполняют обе эти функции
- оба находятся в точке A, так что переменная
size
равна 3 - условие B равно
true
для обеих программ - оба считывают C одновременно
- переменная
db
равна нулю для обеих программ, поэтому условие C равно false
- теперь обе программы собираются в E и создают одну и ту же базу данных 2 раза, это плохо
Или мне нужно блокировать / разблокировать все один раз в этой ситуации?
func (r *Redis) RedisDb(dbId DatabaseId) *RedisDb {
r.Mu().Lock()
defer r.Mu().Unlock()
size := len(r.redisDbs)
if size >= int(dbId) {
db := r.redisDbs[dbId]
if db != nil {
return db
}
}
// create db...
}
Решение
func (r *Redis) RedisDb(dbId DatabaseId) *RedisDb {
getDb := func() *RedisDb { // returns nil if db not exists
if len(r.redisDbs) >= int(dbId) {
db := r.redisDbs[dbId]
if db != nil {
return db
}
}
return nil
}
r.Mu().RLock()
db := getDb()
r.Mu().RUnlock()
if db != nil {
return db
}
// create db
r.Mu().Lock()
defer r.Mu().Unlock()
// check if db does not exists again since
// multiple "mutex readers" can come to this point
db = getDb()
if db != nil {
return db
}
// now really create it
// ...
}