什么时候可以在不锁定的情况下安全地访问受互斥锁保护的变量?

When can safely access mutex protected variable without locking?

在我的代码中存储配置的一个常见模式是 "map[string]interface{}" 受 RWMutex 保护,但通常在应用程序启动后(可以在多个 go-routine 中触发),地图变为完全只读。所以我有一种感觉,从某个时间点开始,读取时的 RWMutex 应该是不必要的。

此配置映射的示例位于 http://play.golang.org/p/tkbj9DBok_

让我想到这一点的一个事实是在一些生产代码中它实际上是以这种方式对共享对象进行不受保护的访问(尽管它在初始化后主要是只读的),我理解使用 RWMutex 的正常方式保护,但有趣的是这个格式错误的代码在过去几个月里没有 运行 出现问题。

在一些准确的 "time point" 写入从缓存刷新到内存并保证不再需要写入之后,读取实际上可以没有 RWMutex.RLock 是真的吗?如果是,无锁访问的时间点是什么时候或者如何设置条件?

RLockRUnlock 是非常快的操作。如果没有写入器,它们基本上是无锁的,并且每个只进行 1 个原子操作 (http://golang.org/src/sync/rwmutex.go?h=RLock#L29)。因此,除非您的应用程序性能不佳,因为读取配置很慢,否则我建议使用 RWLock.

请注意,我最初的回答是建议为 Register 实施 Freeze() 操作,但后来我意识到正确的实施不会比使用 RWLock 更快。

只要没有人在修改地图,多个线程同时读取它应该是安全的。不幸的是,如果没有任何锁定,您将无法确保在您要更新地图时没有其他人正在阅读地图。

因此,一种解决方案是从不更新地图,而是自动替换它。这里可以使用 read-copy-update 算法。而不是直接访问地图,因此您需要取消引用指针才能访问地图。要更新它,您可以执行以下操作:

  1. 获取 "update lock" 互斥体。
  2. 复制地图。您想手动复制所有 keys/values:简单赋值不起作用,因为映射是引用类型。
  3. 对地图副本进行更改。
  4. 使用 sync/atomic 包中的 StorePointer 自动更新指向实时地图的指针以指向您的新地图。
  5. 释放互斥。

在 (4) 中的原子更新之前运行的所有内容都将看到旧地图,之后的所有内容将看到新地图。这些 goroutine 绝不会从正在写入的地图中读取,因此不需要 RWMutex.