c++ - 如果 8 字节由不同线程写入,则现代 intel x86 上的 8 字节读取是否能保证正常?

标签 c++ multithreading x86 atomic

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++ 对象保留在寄存器中,仅最终存储最终值。 (包括全局变量或某个指针指向的内存。)

因为你还不知道volatileatomic<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/

相关文章:

c++ - 我们如何防止控制台窗口在 Visual Studio 下显示?

c++ - 如何仅对 10 x 10 矩阵的右四分之一进行排序?

assembly - 在 c/asm x86 中重新编程中断

汇编语言随机数生成器

c++ - cdecl 调用约定如何破坏 ESP?

c++ - 使用 move 成本低但复制成本高的对象初始化容器的首选方法是什么

c++ - 在 gcc 4.4.7 Red Hat 6.6 上运行 gcc 5.2 build

java - 服务并发请求的 Tomcat 服务器

java - 在线程启动前获取线程的 ThreadID

c# - 确保类是线程安全的,只有属性(任何专门用于此的 .NET 4.0 技术?)