C - 访问整数变量时的安全性:1 位作者,N 位读者

C - safety when accessing an integer variable: 1 writer, N readers

我有一个 "static 64-bit integer variable" 仅由一个线程更新。 所有其他线程仅从中读取。

出于安全原因,我是否应该使用原子操作(例如“__sync_add_and_fetch”)保护此变量?

还是可以直接从(resp.to)读取(resp.write)?

我仍然很困惑,因为我没有找到明确的答案。我不知道我是否必须保护它:

  1. 仅在写入时
  2. 用于写入和读取 (__sync_add_and_fetch(V, 0))
  3. 根本不需要保护

谢谢。

作为一个简单的答案,我建议你只在你写的时候保护它(不,直接写是不行的)。

虽然您知道如果您正在做类似的事情并且线程不同步,您每次都可能得到不同的结果。

PS。我希望这是一条评论,但我的代表太低

通常情况下,读写一个存储位置的原子性是一样的。

也就是说,如果一个位置不能被原子写入,它也不能被原子读取,反之亦然。

如果需要特殊的原子写,除非读也是原子的,否则使用它是没有意义的。

例如,假设正在使用普通读取读取 64 位位置,这需要两次 32 位访问。假设写入发生在这两个访问之间。读取将从新值中获取第二个 32 位,并将其与过时的前 32 位组合。写入不能介于读取的两半之间的唯一方法是读取是原子的。原子读知道如何正确地与原子写交互以防止这种情况。

您可能会对 "exceptions" 这条规则感到困惑。在某些系统中,您可能会看到原子更新(例如增量)与普通读取混合在一起。这是基于读取和写入实际上是原子的假设;特殊的原子增量仅用于使 read/modify/write 循环从并发写入者的角度看起来不可分割:如果 N 个写入者大约同时执行此增量,它确保该位置增加 N.

有时您可能还会看到使用普通读取的正确优化,即使底层数据类型不是原子访问的。在这种情况下,算法并不关心它读取的是 "half baked" 值。

例如,为了简单地监视内存位置以检测更改,您不需要原子读取。检测更改不需要检索正确的值。例如 0x00000000 更新为 0x00010001,但非原子读取观察到中间值 0x00010000,这仍然足以检测位置已更改。

如果您必须确保 readers 永远不会看到半生不熟的值,那么请使用原子读写。

还有其他问题,例如订购。假设一个写者更新了两个位置,A 和 B。在一些计算系统中,a reader 可以在 A 之前观察到 B 的更新。特殊的 "barrier" 或 "fence" 指令有除了任何原子更新指令外还要使用。

在高级语言中,这些障碍的 API 可能内置于某些原子操作的语义中,因此您最终可能只是为了这些障碍而使用原子指令,即使如果数据是原子的。

是的。你需要一个 reader-writer 锁。这正是他们所做的。写作时的障碍,除此之外 readers 可以随意阅读

如果您使用的是 boost,我相信它是 boost::shared_mutex

C++ 当前不支持 reader-writer 锁,尽管您可以自己实现它们。

有关 reader-writer 锁的更多信息请查看 here

如果目标体系结构支持 64 位值的原子 read/write,那么您根本不需要保护:编译器会自动使用相应的指令。在这种情况下最好使用 'volatile' 修饰符,这样可以防止编译器在单个 reader.

中读取值两次

否则,您应该在更新程序线程中写入和在 reader 线程中读取周围使用某种临界区。可能是mutex,read-write lock,seq-lock等

pure "C" 似乎没有精确的方法来检测给定机器是否支持 64 位值的原子 read/write。但是有很多方法可以检测机器本身是否是 64 位的。您可以安全地假设 64 位机器支持 64 位原子 read/write.