C++编译器如何确保在不同的线程上安全地使用不同但相邻的内存位置?

Nat*_*mal 43 c++ multithreading thread-safety

可以说我有一个结构:

struct Foo {
  char a;  // read and written to by thread 1 only
  char b;  // read and written to by thread 2 only
};
Run Code Online (Sandbox Code Playgroud)

根据我的理解,当两个线程在两个不同的内存位置上运行时,C++标准保证了上述的安全性.

我想尽管如此,因为char a和char b属于同一个缓存行,所以编译器必须进行额外的同步.

这到底发生了什么?

Ser*_*eyA 35

这取决于硬件.在我熟悉的硬件上,C++不需要做任何特殊的事情,因为从硬件角度来看,即使在缓存行上访问不同的字节也是"透明地"处理的.从硬件上看,这种情况并没有什么不同

char a[2];
// or
char a, b;
Run Code Online (Sandbox Code Playgroud)

在上面的例子中,我们讨论的是两个相邻的对象,保证可以独立访问.

但是,我把'透明'放在引号中是有原因的.当你真的有这样的情况时,你可能会因为"错误共享"而遭受(性能方面的) - 当两个(或更多)线程同时访问相邻内存并且最终被缓存在几个CPU的缓存中时会发生这种情况.这导致持续的高速缓存失效.在现实生活中,应该注意尽可能防止这种情况发生.

  • @ArtB没有硬性规定.从头开始正确设计程序始终是最好的方法.您还可以尝试分析工具,例如valgrind,并分析缓存未命中数. (3认同)
  • "应该注意尽可能防止这种情况发生.你怎么建议去做呢? (2认同)

Arn*_*gel 21

正如其他人所解释的那样,在通用硬件上没有特别之处 但是,有一个问题:编译器必须避免执行某些优化,除非它可以证明其他线程不访问有问题的内存位置,例如:

std::array<std::uint8_t, 8u> c;

void f()
{
    c[0] ^= 0xfa;
    c[3] ^= 0x10;
    c[6] ^= 0x8b;
    c[7] ^= 0x92;
}
Run Code Online (Sandbox Code Playgroud)

这里,在单线程内存模型中,编译器可以发出如下代码(伪程序集;假设小端硬件):

load r0, *(std::uint64_t *) &c[0]
xor r0, 0x928b0000100000fa
store r0, *(std::uint64_t *) &c[0]
Run Code Online (Sandbox Code Playgroud)

在普通硬件上,这可能比单个字节更快.但是,它会读取和写入c索引1,2,4和5中未受影响(和未提及)的元素.如果其他线程同时写入这些内存位置,则可能会覆盖这些更改.

因此,在多线程内存模型中,这些优化通常无法使用.只要编译器仅执行匹配长度的加载和存储,或仅在没有间隙的情况下合并访问(例如,访问c[6]并且c[7]仍然可以合并),硬件通常已经为正确执行提供了必要的保证.

(也就是说,有一些架构具有弱且违反直觉的内存顺序保证,例如DEC Alpha不像其他架构那样跟踪指针作为数据依赖关系,因此有必要在某些架构中引入显式内存屏障的情况下,在低级别的代码,有是一个有些知名的小咆哮由Linus Torvalds在这个问题上.但是,标准的C++实现预期从这样的问题保护你.)