C++中两个原子变量的条件更新

ltqd579y  于 2023-01-14  发布在  其他
关注(0)|答案(2)|浏览(205)

我想在if条件下更新两个原子变量,if条件使用其中一个原子变量。我不确定这两个原子变量是否会一起更新。
下面是一个多线程代码。在“if(local〉a1)”中,a1是一个原子变量,因此阅读if条件是否是跨线程的原子变量。换句话说,如果线程t1处于if条件,线程t2是否会等待线程t1更新a1?是否有可能一个线程更新a2,而另一个线程更新a1?

// constructing atomics
#include <iostream>       // std::cout
#include <atomic>         // std::atomic
#include <thread>         // std::thread
#include <vector>         // std::vector

std::atomic<int> a1{0};
std::atomic<int> a2{0};

void count1m (int id) {
         double local = id;
         double local2 = id*3;
         *if(local > a1) {*      // a1 is an atomic variable so will reading it in if condition be atomic across threads or not?
                a1 = local;
                a2 = local2;
        }
 };

int main ()
{
        std::vector<std::thread> threads;
        std::cout << "spawning 20 threads that count to 1 million...\n";
        for (int i=20; i>=0; --i) {
           threads.push_back(std::thread(count1m,i));
        }
        
        for (auto& th : threads) th.join();
        cout << "a1 = " << a1 << endl;                                                                                                  
}
icnyk63a

icnyk63a1#

我不确定这两个原子变量是否会一起更新。

不是

原子意味着 * 不可分割 *,因为对原子的写入不能被读到一半,处于中间或不完整的状态。
然而,对一个原子的更新并不与对另一个原子的更新一起批处理,编译器如何知道 * 哪些 * 更新应该像这样批处理呢?
如果你有两个原子变量,你就有两个独立的对象,它们都不能被单独观察到部分写入状态,你仍然可以同时读取它们,并看到另一个线程更新了一个而没有更新另一个的状态,即使代码中的存储是相邻的。
可能性包括:
1.使用互斥锁即可。
你在评论中排除了这一点,但为了完整起见,我还是要提到它,因为这是迄今为止最简单的方法。
1.将两个对象打包到一个原子中。
注意,如果你的平台没有原生的128位原子,一个128位对象(足够大,可以容纳两个binary 64 double)可能需要在内部使用互斥锁或类似的同步原语,你可以检查std::atomic<DoublePair>::is_lock_free()来找出答案(对于一个合适的包含一对double的结构体DoublePair)。
在互斥禁止下,非无锁原子是否可接受,我无法猜测。

  • 制定一个精心设计的无锁同步协议,例如:
  • 将索引存储到DoublePair对象的循环数组中,并原子地更新 * 那个 *(对于多个生产者,有各种各样的方案,但单个生产者肯定更简单--不要忘记A-B-A保护)
  • 使用原始互斥体、信号量或其他一些技术上不属于互斥体的同步原语
  • 使用原子来编写自旋锁(技术上也不是互斥锁,但我还是无法猜测它是否适合您)

主要的问题是你说你不允许使用互斥锁,但是你没有说“为什么”。代码必须是无锁的吗?无等待的吗?有人真的讨厌std::mutex,但是会接受任何其他的同步原语吗?

wribegjk

wribegjk2#

基本上有两种方法可以做到这一点,它们是不同的。
第一种方法是创建一个可以立即更新的原子结构。注意,使用这种方法时,存在争用条件,在aip更新之前,localaip.a1之间的比较可能会发生变化。

struct IntPair {
    int a1;
    int a2;
};

std::atomic<IntPair> aip = IntPair{0,0};

void count1m (int id) {
         double local = id;
         double local2 = id*3;
         if(local > aip.load().a1) {
                aip = IntPair{int(local),int(local2)};
        }
};

第二种方法是使用互斥锁来同步整个节,如下所示。这将保证不会发生竞态条件,并且所有操作都是原子完成的。我们使用了std::lock_guard来提高安全性,而不是手动调用m.lock()m.unlock()

IntPair ip{0,0};
std::mutex m;
void count1m (int id) {
         double local = id;
         double local2 = id*3;
         std::lock_guard<std::mutex> g(m);
         if(local > ip.a1) {
                ip = IntPair{int(local),int(local2)};
        }
 };

相关问题