the*_*ang 4 c++ x86 multithreading atomic
struct Data {
double a;
double b;
double c;
};
Run Code Online (Sandbox Code Playgroud)
如果在不同的线程上读取,但只有一个其他线程正在写入 a、b、c 中的每一个,那么读取每个双精度值是否正常?
如果我确保Data对齐,会出现什么情况?
struct Data {double a,b,c; } __attribute__((aligned(64));
Run Code Online (Sandbox Code Playgroud)
这将确保 a,b,c 中的每一个都对齐到 64,64+8, 64+16... 所以总是对齐到 8*8=64 位边界。
这个问题对原子 x86 指令的对齐要求及其答案让我认为Data::a/b/c从另一个线程写入并同时读取它们而不使用std::atomic.
是的,我知道std::atomic会解决这个问题,但这不是问题。
是的,从 P5 Pentium 开始,x86 ISA 保证对齐的 8 字节加载/存储是原子的。为什么在 x86 上自然对齐的变量原子上的整数赋值是原子的?
但这是 C++;不能保证 store 和 reloads 不会被优化掉。在一个线程中写入并在另一个线程中读取是 C++ 未定义行为;允许编译器假设它不会发生,打破天真的假设。这让他们可以在多次读取/写入时将 C++ 对象保存在寄存器中,最终只存储最终值。(包括某些指针指向的全局变量或内存。)
由于您还不知道volatile或atomic<double>因此需要,最好阅读有关为您做的其他事情atomic<>,例如订购 wrt。除非您使用其他操作memory_order_relaxed(默认情况下seq_cst这会使商店变得昂贵,但在 x86 负载上仍然同样便宜)。并且(例如volatile)假设其他线程可能在此线程中的访问之间修改了对象。请参阅“int num”的 num++ 可以是原子的吗?,其中一些与 FP 加载和存储相关。
C++ 中的无锁编程并不简单,除非您对同步/排序的需求为零。然后你“只是”必须确保你告诉编译器你的意思,用atomic<T>,或者作为一个 hack 用double.
由于 GCC 的std::atomic<double>withmo_relaxed不能有效编译,如果您只关心可移植性,您可能希望通过创建成员来推出自己的volatile。(甚至可以转换为(volatile double*)喜欢 Linux 内核的READ_ONCE/WRITE_ONCE宏)。使用 clang,您可以只使用atomic<double>memory_order_relaxed 并且事情将有效地编译。参见C++20 std::atomic<float>- std::atomic<double>.specializations例如你在 C++20 之前可以做什么;C++20 只添加原子 RMW add/sub fordouble所以你不必用 CAS 循环滚动你自己的。
volatile可能仍然会击败自动矢量化,但您当然可以使用_mm_load_pd或其他任何东西。(另请参阅x86_64 上的原子双浮点或 SSE/AVX 向量加载/存储- 请注意,即使对齐,SIMD 加载/存储也不一定是原子的。同样未记录的是它们是否是每个元素原子的,尽管我认为这是可以安全地假设。 矢量加载/存储和收集/分散的每元素原子性?)
什么时候在多线程中使用 volatile?通常永远不会,除非作为 GCC 的一种解决方法,它不会发出有效的 asm for atomic<double>,并且我们确切地知道如何volatile编译为 asm。
顺便说一句,您只需要alignas(8)确保成员是 8 字节对齐的。 将结构与整个缓存行对齐不会有什么坏处,除非它浪费空间。
对于性能:如果不同的线程在同一个缓存行中使用不同的变量,那就是“错误共享”并且对性能来说很糟糕。 不要将共享变量组合在一个结构中,除非它们通常作为一个组读取或写入。 否则,您肯定希望它们位于单独的 64 字节缓存行中。
请注意,a 上的数据竞争volatile仍然是 ISO C++ 未定义行为,但如果您使用的是 GNU C(根据您的 要求__attribute__),它的定义非常好。Linux 内核将它用于自己的手动原子(以及内联asm屏障),因此您可以假设它不会很快被有意取消支持。
TL:DR:在 GNU C 中,它或多或少地将volatile视为原子性的工作mo_relaxed,因为对齐的对象小到足以自然成为原子性。
| 归档时间: |
|
| 查看次数: |
209 次 |
| 最近记录: |