【问题标题】:Does setting a bit collide with concurrent sets of other bits on the same word?设置一个位是否与同一个单词上的其他位的并发集合冲突?
【发布时间】:2010-11-09 12:02:04
【问题描述】:
  1. 假设我有一个位图,并且几个线程(在多个 CPU 上运行)正在对其进行设置。没有使用同步,也没有原子操作。此外,没有进行任何重置。据我了解,当两个线程试图在同一个字上设置两个位时,最终只有一个操作会坚持。原因是对于要设置的位,应该读取和写回整个字,因此当两个读取同时完成时,在写回时一个操作将覆盖另一个操作。对吗?

  2. 如果上述情况属实,那么字节操作是否也总是如此?也就是说,如果一个词是 2 个字节,并且每个线程都尝试将不同的字节设置为 1,那么它们在并发执行时是否也会相互覆盖,或者某些系统是否支持仅将结果写回一个词的一部分?

询问的原因是想弄清楚我必须放弃多少空间才能在位/字节/字映射操作中省略同步。

【问题讨论】:

  • 在 C++11 中,对单独的 char 对象的访问保证不是数据竞争。但在同一个char 中,这当然是个问题。 std::bitmap 经常使用 unsigned long 块,使冲突的可能性增加 8 倍。在 asm 中,请参阅 How to set bits of a bit vector efficiently in parallel? 以了解处理两个线程相互更新到同一字节的想法。 (例如,做一些更新,mfence,然后回读以确保它们都通过了。但是 C++ 在让您在需要时混合原子负载和更宽的大小方面没有帮助。

标签: c++ memory assembly synchronization bit-manipulation


【解决方案1】:

简而言之,它非常依赖 CPU 和编译器。

假设你有一个包含零的 32 位值,线程 A 想要设置位 0,线程 B 想要设置位 1。

正如您所描述的,这些是读取-修改-写入操作,同步问题是“如果它们发生冲突会发生什么”。

你需要避免的情况是:

A: Reads (gets 0)
B: Reads (also gets zero)
A: Logical-OR bit 0, result = 1
A: Writes 1
B: Logical-OR bit 1, result = 2
B: Writes 2 - oops, should have been 3

...当正确的结果是这样...

A: Reads (gets 0)
A: Logical-OR bit 0, result = 1
A: Writes 1
B: Reads (gets 1)
B: Logical-OR bit 1, result = 2
B: Writes 3 - correct

在某些处理器上,读取-修改写入将是三个单独的指令,因此您需要同步。在其他情况下,它将是单个原子指令。在多个核心/CPU 系统上,这将是一条指令,但其他核心/CPU 可能能够访问,因此您需要再次同步。

用字节来做也是一样的。在某些处理器内存架构中,您只能写入 32 位内存值,因此字节更新需要像以前一样进行 read-modify-write。

X86 架构(特别是 Windows)更新

Windows 提供了一组对 32 位值的原子“互锁”操作,包括 Logical OR。这些可能对您避免关键部分有很大帮助。但要小心,因为正如 Raymond Chen 指出的那样,they don't solve everything。继续阅读该帖子,直到您理解为止!

【讨论】:

  • Interlocked* 函数实际上与临界区相当,至少在两个线程尝试同时进入一个区之前,当一个真正的内核对象(事件 IIRC)被创建时。到那时,Interlocked 可用于任何入口。另外,请注意,Interlocked ops 确实在 SMP 环境中施加了一些同步,并且比简单的集合重得多。但无论如何感谢您的详细回答!
【解决方案2】:

细节将取决于系统,也可能取决于编译器。我想您可能必须一直使用 32 位整数才能摆脱您担心的影响。

【讨论】:

  • 我什至不确定 32 位值是否足够 - 所有 64 位架构上的 32 位操作都是原子操作吗?
  • @MSalters:在所有 32 位架构上,32 位操作也不一定是原子的(在我们需要的意义上)。 ARM 允许不连贯的缓存,因此操作是否是原子的,与本地 CPU 缓存无关,它不是原子的,与主内存无关。
【解决方案3】:
  1. 出于您指定的原因,我相信这是真的。

  2. 在我看来,如果您的位图存储为char[],并且您的架构是字节可寻址的(可以在内存中读取和写入单个字节,而无需读取整个),那么编译器可能会生成一个原子操作。即便如此,它是完全由实现定义的,所以你不能依赖它。

【讨论】:

  • 我以一般的方式提出了这个问题,但在实践中,我的目标是常见的 x86 和 x64 平台。它们的任何变体是字节可寻址的,还是某些奇异架构的特性?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-13
  • 2013-03-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-27
相关资源
最近更新 更多