互斥锁用对了吗?

Mutex used right?

我对 mutex locken/unlocking 一次又一次感到有点困惑。 我正在使用 RWMutex 并且所有 goroutines 当然会有相同的互斥量。

如此频繁地使用互斥体时,这段代码是否仍然受到竞争保护?

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...
}

我认为可能发生的示例情况:

  1. gorountine1和goroutine2都是运行这个函数
  2. 都在A点所以变量size是3
  3. 两个 goroutines 的条件 B 都是 true
  4. 两人同时读C
  5. 变量db对于两个goroutines都是nil所以条件C是false
  6. 现在两个 goroutine 都将转到 E 并创建相同的数据库 2 次,这很糟糕

还是在这种情况下我必须 lock/unlock 一次?

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
    // ...
}

欢迎来到同步世界。您的评估是正确的,第一次实施可能会出现并发问题。对于第二个,那些并发问题被消除了,但它被完全锁定,甚至没有机会进行并发读取访问。您 没有 那样做,您可以使用读锁进行初始检查,然后如果该检查确定需要创建,则建立写锁,然后重新检查, 如果仍然需要则创建,然后解锁。这不是一个不寻常的结构。它的效率较低(由于执行了两次检查)因此您需要权衡取舍,主要取决于执行两次检查的成本以及该功能能够在只读路径。