Go 互斥使用对吗?

Go 互斥使用对吗?,go,concurrency,mutex,race-condition,goroutine,Go,Concurrency,Mutex,Race Condition,Goroutine,我对一次又一次地锁定/解锁互斥锁感到有点困惑。 我使用的是a,所有Goroutine当然都有相同的互斥 当经常使用互斥锁时,这段代码仍然受种族保护吗 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().

我对一次又一次地锁定/解锁互斥锁感到有点困惑。 我使用的是a,所有Goroutine当然都有相同的互斥

当经常使用互斥锁时,这段代码仍然受种族保护吗

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
  • 两个goroutine的条件B均为
    true
  • 两者同时读取C
  • 变量
    db
    对于两个goroutine都是nil,因此条件C是
    false
  • 现在两个goroutine都将重复创建相同的数据库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
        // ...
    }
    

    欢迎来到同步世界。您的评估是正确的,第一次实现可能会出现并发问题。对于第二个,这些并发性问题被删除,但它被完全锁定,甚至没有并发读取访问的机会。您不必这样做,您可以使用读锁进行初始检查,然后如果该检查确定需要创建,则建立写锁,然后重新检查,如果仍然需要创建,然后解锁。这不是一个不寻常的构造。它的效率较低(由于执行了两次检查),因此您可以根据两次检查的成本以及函数能够在只读路径中运行的频率来决定取舍。谢谢,是的,读取路径比创建路径更频繁,你的意思是像我现在发布的代码一样吗?:)