C 17原子和条件_Variable僵局

C++17 atomics and condition_variable deadlock

本文关键字:Variable 僵局 条件      更新时间:2023-10-16

我有以下代码,该代码在评论的行上僵持。基本上F1和F2作为程序中的单个线程运行。F1期望我是1,并将其减少,并通知CV。F2期望我为0并将其递增,并通知CV。我假设如果F2增量到1,请致电CV.Notify((,则会发生僵局,然后F1读取i(0(的陈旧值,因为Mutex和i之间没有内存同步,然后再等待,然后永远不会被唤醒。向上。然后F2也进入睡眠状态,现在两个线程都在等待的简历,这将永远不会通知。

如何编写此代码,以免发生僵局?基本上,我想实现的目标是具有通过两个线程更新的一些原子状态。如果状态在其中一个线程中不正确,我不想旋转;相反,我想使用CV功能(或类似的内容(在值正确时唤醒线程。

我正在使用G -7用O3编译代码(尽管僵局都在O0和O3中发生(。

#include <atomic>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
std::atomic_size_t i{0};
std::mutex mut;
std::condition_variable cv;
void f1() {
  while (1) {
    {
      std::unique_lock<std::mutex> lk(mut);
      cv.wait(lk, []() { return i.load() > 0; }); // deadlocks
    }
    --i;
    cv.notify_one();
    std::cout << "i = " << i << std::endl; // Only to avoid optimization
  }
}
void f2() {
  while (1) {
    {
      std::unique_lock<std::mutex> lk(mut);
      cv.wait(lk, []() { return i.load() < 1; }); // deadlocks
    }
    ++i;
    cv.notify_one();
    std::cout << "i = " << i << std::endl; // Only to avoid optimization
  }
}
int main() {
  std::thread t1(f1);
  std::thread t2(f2);
  t1.join();
  t2.join();
  return 0;
}

编辑:COUT仅是为了避免编译器优化。

我认为问题是可以更改i的值,并且在另一个线程评估return i.load() > 0;后,但是在Lambda呼叫返回和CV简历等待之前,可以在间隔中调用notify_one。这样,另一个线程就不会观察到原子变量的变化,也没有人可以唤醒它以再次检查。这可以通过锁定变量时锁定静音来解决,尽管这样做会破坏原子的目的。

我认为VTT的答案是正确的,只想显示会发生什么。首先,可以将代码重写为以下形式:

void f1() {
   while (1) {
      {
         std::unique_lock<std::mutex> lk(mut);
         while (i == 0) cv.wait(lk);
      }
      --i;
      cv.notify_one();
   }
}
void f2() {
   while (1) {
      {
         std::unique_lock<std::mutex> lk(mut);
         while (i >= 1) cv.wait(lk);
      }
      ++i;
      cv.notify_one();
   }
}

现在,考虑以下时间行,i最初是0

time step    f1:               f2:
=========    ================= ================
        1                      locks mut
        2                      while (i >= 1) F
        3                      unlocks mut
        4    locks mut
        5    while (i == 0) T                  
        6                      ++i;
        7                      cv.notify_one();
        8    cv.wait(lk);
        9    unlocks mut(lk) 
       10                      locks mut                   
       11                      while (i >= 1) T
       12                      cv.wait(lk);

有效地,f1i1时等待。这两个线程现在都立即以阻止状态等待。


解决方案是将i的修改放入锁定部分。然后,i甚至不需要是原子变量。

当线程不拥有Mutex时,您可以调用cv.notify_one();。它可能导致通知被发送为空。想象一下f2f1之前开始。f2调用cv.notify_one();,但f1尚未在cv.wait中。

获得的Mutex保证f2std::unique_lock<std::mutex> lk(mut)中或等待通知。

#include <atomic>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
std::atomic_size_t i{0};
std::mutex mut;
std::condition_variable cv;
void f1() {
  while (1) {
    std::size_t ii;
    {
      std::unique_lock<std::mutex> lk(mut);
      cv.wait(lk, []() { return i.load() > 0; });
      ii = --i;
      cv.notify_one();
    }
    std::cout << "i = " << ii << std::endl;
  }
}
void f2() {
  while (1) {
    std::size_t ii;
    {
      std::unique_lock<std::mutex> lk(mut);
      cv.wait(lk, []() { return i.load() < 1; });
      ii = ++i;
      cv.notify_one();
    }
    std::cout << "i = " << ii << std::endl;
  }
}
int main() {
  std::thread t1(f1);
  std::thread t2(f2);
  t1.join();
  t2.join();
  return 0;
}

btw std::atomic_size_t i可以是std::size_t i

,因为 i 是原子,因此无需使用互斥s守护其修改。

f1 f2 在条件变量上的等待,除非发生虚假唤醒,否则请等待,因为条件变量永远不会通知。由于不能保证虚假唤醒,我建议在等待条件变量之前检查条件,并最终通知其他线程的条件变量。

您的代码还有另一个问题。这两个功能 f1 f2 将永远不会结束。因此,您的 Main 功能将等待加入其线程。