【问题标题】:Double checked locking in golang - Why mutex.RLock() is required?golang 中的双重检查锁定 - 为什么需要 mutex.RLock()?
【发布时间】:2019-01-08 13:34:04
【问题描述】:

我有一段来自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 时都会获取并释放读锁。

在获取读锁之前是否应该再进行一次 nil 检查,例如:

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()
        }
    }
}

第一次 nil 检查将确保 RLock 不会被不必要地使用。我对么?

【问题讨论】:

  • hmm,我很想知道人们要说什么 - 我看起来不需要 RLock,nil 检查就足够了 - 也许我遗漏了一些东西,这可能取决于更广泛的背景,但我想想只要你检查 nil,获取锁,然后检查它仍然是 nil(以防另一个线程在那微秒内初始化)它应该没问题
  • 最后一句没看懂。为什么你认为应该有一个额外的零检查?
  • 认为另一个线程可能会在检查 nil 和获取锁之间的瞬间初始化,尽管您似乎建议您的答案中需要 rlock
  • 但是已经有第二次 nil 检查了...
  • 第二种实现容易受到锁应该防止的竞争条件的影响。 syncProducer 在没有至少一个读锁的情况下不能被读取,并且在没有写锁的情况下不能被分配。

标签: go


【解决方案1】:

如果你没有获得 RLock 来读取 syncProducer,这是一个数据竞争,因为另一个 goroutine 可能会更新它。

如果您假设对指针变量的读取/写入是原子的,这看起来是无害的——syncProducer 的活泼读取永远不会导致错误行为。如果你不做这个原子假设,如果你运气不好,读取可能只产生指针的一些字节,你的程序就会崩溃。

这可能会也可能不会,具体取决于您使用的架构、机器字长、编译器版本等。但是RLock 避免了任何担忧。

与其明确地搞乱 RWLocks,不如使用它(假设目标是懒惰地初始化一个变量):

var (
    syncOnce sync.Once
    syncProducerInternal *syncProducerType
)

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

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

【讨论】:

  • 查看这个 SO 问题以获得解释:go assignments not保证是原子的:stackoverflow.com/questions/34750323/…
  • 我认为这不适用于 nil 检查 - 有些东西要么是 nil,要么不是,它不能是部分 nil
  • @SwiftD 指针可以在读取时部分写入 RAM。然后你可能会得到一些 0 字节和一些指针字节。所以不是零,但也不是一个有效的指针。正如我在问题中所说,这是否真的会发生取决于架构等。如果您稍后在读取变量时获得读取锁,仍然可能没问题,但我想这段代码的重点是避免这种情况。
  • 锁定launchdarkly.com/blog/…的好解释
  • @thst 谢谢你——我刚刚在我的答案中独立添加了一个 sync.Once 示例,但我看到文章最后暗示了它......
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-12
    • 2021-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多