如果不同线程写入 8 字节,是否保证在现代英特尔 x86 上读取 8 字节是正常的?

Is a read of 8 bytes on modern intel x86 guaranteed to by sane if 8byte written by different thread?

struct Data {
    double a;
    double b;
    double c;
};

如果在不同的线程上读取,但只有一个其他线程正在写入每个 a、b、c,每个 double 的读取是否正常?

如果我确保 Data 对齐会出现什么情况?

struct Data {double a,b,c; } __attribute__((aligned(64));

这将确保每个 a、b、c 都与 64,64+8、64+16...对齐,因此始终与 8*8=64 位边界对齐。

这个问题 alignment requirements for atomic x86 instructions 及其答案让我认为从另一个线程写入 Data::a/b/c 并在不使用 std::atomic.[=17= 的情况下同时读取它们是完全有效的]

是的,我知道 std::atomic 可以解决这个问题,但这不是问题所在。

是的,从 P5 Pentium 开始,x86 ISA 保证对齐 8 字节 loads/stores 是原子的。

但这是C++;不能保证存储和重新加载不会被优化掉。在一个线程中写入并在另一个线程中读取是 C++ 未定义行为;允许编译器假设它不会发生,breaking naive assumptions。这让他们可以将 C++ 对象保存在多个 reads/writes 的寄存器中,最终只存储最终值。 (包括全局变量或某些指针指向的内存。)

由于您还不知道 volatileatomic<double> 是出于这个原因,最好阅读 其他 [=12] =] 为你做,就像订购 wrt。其他操作,除非您使用 memory_order_relaxed(默认值为 seq_cst,这会使存储变得昂贵,但在 x86 上加载仍然同样便宜)。并且(如 volatile)假设其他线程可能已经在该线程的访问之间修改了一个对象。请参阅 ,其中一些与 FP 加载和存储相关。

C++ 中的无锁编程 并不 简单,除非您对同步/排序的需求为零。然后你 "just" 必须确保你告诉编译器你的意思,用 atomic<T>,或者用 double.

作为 hack

由于 GCC 的 std::atomic<double>mo_relaxed 编译效率不高,您 可能 想通过创建成员 volatile 来滚动自己的 volatile 如果你只关心便携性。 (甚至像 Linux 内核的 READ_ONCE / WRITE_ONCE 宏一样转换为 (volatile double*) )。使用 clang,您只需将 atomic<double> 与 memory_order_relaxed 一起使用,即可高效编译。有关在 C++20 之前可以做什么的示例,请参阅 C++20 std::atomic<float>- std::atomic<double>.specializations; C++20 只为 double 添加了原子 RMW add/sub,所以你不必自己使用 CAS 循环。

volatile 可能仍然会打败自动矢量化,但您当然可以使用 _mm_load_pd 或其他任何东西。 (另见 - note that SIMD load/store aren't necessarily atomic even if aligned. Also undocumented is whether they're per-element atomic, although that is I think safe to assume. Per-element atomicity of vector load/store and gather/scatter?

When to use volatile with multi threading? 通常从不,除非可能作为 GCC 的解决方法,它不会为 atomic<double> 发出有效的 asm,并且我们确切地知道 volatile 是如何编译为 asm.


顺便说一句,你只需要 alignas(8) 来确保成员是 8 字节对齐的。 将结构与整个缓存行对齐不会造成伤害,除非它浪费 space.

为了性能:如果不同的线程在同一缓存行中使用不同的变量,那是 "false sharing" 并且性能很糟糕。 不要将您的共享变量组合在一个结构中,除非它们通常作为一个组来读取或写入。否则您肯定希望它们位于单独的 64 字节缓存行中。


请注意 volatile 上的数据争用仍然是 ISO C++ 未定义行为,但如果您使用的是 GNU C(根据您的 __attribute__ 的要求) ,它的定义非常明确。 Linux 内核将它 用于其自己的手动原子(连同内联 asm 用于屏障),因此您可以假设它不会很快有意不受支持。

TL:DR:在 GNU C 中,将 volatilemo_relaxed 视为原子,对于小到足以自然成为原子的对齐对象,或多或少会做一些工作。