struct Data {
double a;
double b;
double c;
};
如果在不同的线程上读取,但只有一个其他线程正在写入 a、b、c 中的每一个,那么每个 double 的读取是否正常?
如果我确保数据
对齐,会出现什么情况?
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 是完全有效的。
是的,我知道 std::atomic
可以解决这个问题,但这不是问题。
最佳答案
是的,从 P5 Pentium 开始,x86 ISA 保证对齐的 8 字节加载/存储是原子的。 Why is integer assignment on a naturally aligned variable atomic on x86?
但这是C++;无法保证存储和重新加载不会被优化。在一个线程中写入并在另一个线程中读取是 C++ 未定义行为;编译器可以假设它不会发生, breaking naive assumptions 。这使他们可以在多次读/写时将 C++ 对象保留在寄存器中,仅最终存储最终值。 (包括全局变量或某个指针指向的内存。)
因为你还不知道volatile
或atomic<double>
由于这个原因,需要更好地阅读 atomic<>
的其他内容为您做的,就像订购 wrt 一样。其他操作,除非您使用 memory_order_relaxed
(默认值为 seq_cst
这使得存储变得昂贵,但在 x86 上负载仍然同样便宜)。并且(如 volatile
)假设其他线程可能在该线程的访问之间修改了对象。请参阅Can num++ be atomic for 'int num'? ,其中一些与 FP 加载和存储相关。
C++ 中的无锁编程并不简单,除非您对同步/排序的需求为零。然后你“只需”确保你告诉编译器你的意思,用 atomic<T>
,或者使用 double
进行黑客攻击.
自 GCC std::atomic<double>
起与 mo_relaxed
无法有效编译,您可能希望通过创建成员 volatile
来推出自己的如果您只关心便携性。 (或者甚至像 Linux 内核的 (volatile double*)
/READ_ONCE
宏一样转换为 WRITE_ONCE
)。使用 clang 你可以使用 atomic<double>
使用 memory_order_relaxed 并且可以有效地编译。请参阅C++20 std::atomic<float>- std::atomic<double>.specializations例如,在 C++20 之前您可以做什么; C++20 只为 double
添加原子 RMW add/sub因此您不必自己使用 CAS 循环。
volatile
可能仍然会击败自动矢量化,但您当然可以使用 _mm_load_pd
管他呢。 (另请参见Atomic double floating point or SSE/AVX vector load/store on x86_64 - 请注意,即使对齐,SIMD 加载/存储也不一定是原子的。此外,它们是否是每个元素的原子也没有记录,尽管我认为可以安全地假设这一点。 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 字节对齐的。将结构与整个缓存行对齐不会有什么坏处,除非浪费空间。
对于性能:如果不同的线程在同一缓存行中使用不同的变量,那就是“错误共享”并且对性能来说很糟糕。 不要将共享变量分组在一个结构中,除非它们通常作为一组进行读取或写入。否则您肯定希望它们位于单独的 64 字节缓存行中。
<小时/>请注意 volatile
上的数据争用仍然是 ISO C++ 未定义行为,但如果您使用 GNU C(根据 __attribute__
的要求),它的定义非常明确。 Linux 内核将其用于自己的手动原子(以及内联 asm
作为屏障),因此您可以假设它不会很快被故意不支持。
TL:DR:在 GNU C 中,它或多或少地需要考虑 volatile
作为原子 mo_relaxed
,对于足够小的对齐对象,自然是原子的。
关于c++ - 如果 8 字节由不同线程写入,则现代 intel x86 上的 8 字节读取是否能保证正常?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59063744/