代码之家  ›  专栏  ›  技术社区  ›  codingenious

在golang中双重检查锁定-为什么需要mutex.rlock()?

go
  •  0
  • codingenious  · 技术社区  · 7 年前

    我有一段代码来自 this website 它对对象初始化的锁定进行了双重检查。

    func checkSyncProducer() {
        mutex.RLock()
        if syncProducer == nil {
            mutex.RUnlock()
            mutex.Lock()
            defer mutex.Unlock()
            if syncProducer == nil {
                syncProducer  = createSyncKafkaProducer() //this func will initialize syncProducer.
            }
        } else {
            defer mutex.RUnlock()
        }
    }
    

    这段代码有 mutex.RLock() 在第一次零检查之前。

    为什么需要这样做?(这在页面中有解释,但我不明白)并且它不会增加开销,因为每次调用checkSyncProducer时,都会获取并释放read lock。

    在获取读锁之前是否还要进行一次零检查,如:

    func checkSyncProducer() {
        if syncProducer == nil {
            mutex.RLock()
            if syncProducer == nil {
                mutex.RUnlock()
                mutex.Lock()
                defer mutex.Unlock()
                if syncProducer == nil {
                    createSyncKafkaProducer()
                }
            } else {
                defer mutex.RUnlock()
            }
        }
    }
    

    第一个零检查将确保RLock不被不必要地占用。我说的对吗?

    2 回复  |  直到 7 年前
        1
  •  4
  •   Paul Hankin    7 年前

    如果你不买一本书来读 syncProducer ,这是一个数据竞赛,因为另一个Goroutine可能会更新它。

    如果假设对指针变量的读/写是原子的,那么这看起来是无害的——对 同步发生器 绝不能导致不正确的行为。如果您不进行这种原子假设,那么如果您不走运,读操作可能只产生指针的一些字节,并且您的程序会崩溃。

    根据您所使用的体系结构、机器字大小、编译器版本等,这可能是可能的,也可能是不可能的。但是 RLock 避免任何顾虑。

    与其显式地乱弄rwlocks,不如使用它(假设目标是延迟初始化变量):

    var (
        syncOnce sync.Once
        syncProducerInternal *syncProducerType
    )
    
    func syncProducer() *syncProducerType {
        syncOnce.Do(func() { syncProducerInternal = createSyncKafkaProducer() })
        return syncProducerInternal
    }
    

    然后需要同步生成器的代码可以调用 syncProducer() 函数来获取它,并且永远不会看到零指针。

        2
  •  2
  •   Jonathan Hall    7 年前

    这是必需的,因为检查是只读操作。相反,使用rw锁是可能的,但这意味着一次只能使用一个goroutine进行检查。