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&lt;Vec&gt;。问题是,如果它太大,则无法使用这些存储/加载的本机 CPU 指令。有什么方法可以使用std::atomic_thread_fence 来保证写入线程在读取线程获取它们之前提交多个原子写入?也就是说,保证写入线程的所有写入都在读取线程看到之前提交?还是std::atomic_thread_fence 只提供线程内的重新排序保证?目前,仅对每个成员使用 .store(std::memory_order_release) 似乎并不能保证所有三个存储都发生在任何读取之前。

显然,我可以在这里使用锁,但理想情况下,我想找到一种方法使这个数据结构无锁。

我知道我可以将highest_xhighest_yhighest_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++ 中多个值的多线程原子存储/加载的主要内容,如果未能解决你的问题,请参考以下文章

多线程并发之原子性

C++ 线程局部变量thread_local

一个经典的多线程同步问题

秒杀多线程第四篇 一个经典的多线程同步问题

纯 C++ 中的多线程?

带返回值的多线程