C++ 中多个值的多线程原子存储/加载
Posted
技术标签:
【中文标题】C++ 中多个值的多线程原子存储/加载【英文标题】:Multithreaded Atomic Store/Load of multiple values in C++ 【发布时间】:2017-01-04 20:31:10 【问题描述】:假设我在 C++ 中有一个结构和类:
struct Vec
double x;
double y;
double z;
class VecTracker
Vec latest_vec;
std::atomic<double> highest_x;
std::atomic<double> highest_y;
std::atomic<double> highest_z;
//updates highest_x, highest_y, highest_z atomically
void push_vec(const Vec& v);
double get_high_x() const;
double get_high_y() const;
double get_high_z() const;
//returns Vec consisting of snapshot of highest_x, highest_y, highest_z
Vec get_highs() const;
我将有 R 个读取器线程和一个写入器线程。编写者线程将更新零个或多个highest_*
成员。如果阅读器线程调用get_highs()
,我需要阅读器线程在阅读器线程读取highest_x
、@987654326 之前之前看到来自编写器线程push_vec()
函数的当前调用的所有写入@等来产生一个向量。
现在,我知道如果Vec
足够小,我可以使用std::atomic<Vec>
。问题是,如果它太大,则无法使用这些存储/加载的本机 CPU 指令。有什么方法可以使用std::atomic_thread_fence
来保证写入线程在读取线程获取它们之前提交多个原子写入?也就是说,保证写入线程的所有写入都在读取线程看到之前提交?还是std::atomic_thread_fence
只提供线程内的重新排序保证?目前,仅对每个成员使用 .store(std::memory_order_release)
似乎并不能保证所有三个存储都发生在任何读取之前。
显然,我可以在这里使用锁,但理想情况下,我想找到一种方法使这个数据结构无锁。
我知道我可以将highest_x
、highest_y
和highest_z
放在一个结构中,并在堆上分配它的两个副本,在每次写入后自动交换指针。这是唯一的方法吗?
【问题讨论】:
【参考方案1】:魔鬼来了://updates highest_x, highest_y, highest_z atomically
。你如何保证它们确实是原子的?由于 3 个双打不适合 16B(我在 X86_64 平台上知道的最大原子操作),因此确保这一点的唯一方法是使用 mutex
。
您的问题不在于栅栏。通过发出栅栏指令,您将保证所有以前的更新都是可见的。但是,您不能保证它们在在此之前不可见。因此,您将能够读取其中一个向量变量的最新值。
要解决您的问题,您应该选择 mutex
- 它们在无竞争时非常有效 - 或者,如果您对互斥锁过敏,您描述自己的指针交换解决方案。
【讨论】:
啊哈。是的,我假设只有本机原子操作才能真正保证原子行为。看起来指针交换是要走的路。在这里不能真正使用互斥锁或自旋锁,因为会有非常激烈的争用,我想保证进展。 我进行了搜索,但找不到任何对 CMPXCHG32B 指令的引用。如果确实存在这样的指令,那么在这种情况下就足够了,因为在今天的大多数系统上,3 个 double 仅相当于 24B。您是否将它与 CMPXCHG16B 混淆了?在这种情况下,你的论点就更有意义了。 @ErikNyström,100% 是的!感谢您的发现。以上是关于C++ 中多个值的多线程原子存储/加载的主要内容,如果未能解决你的问题,请参考以下文章