使用 std::conditional_variable 等待条件

Using std::conditional_variable to wait on a condition

为简单起见,假设我们只有一个条件变量来匹配由布尔值反映的单个条件。

1) 为什么 std::condition_variable::wait(...) 在发送了一个 "notify" 来解除它的休眠后再次锁定互斥体?

2) 看到“1)”中的行为,这是否意味着当您执行 std::condition_variable::notify_all 时,它只会使所有等待线程 unblocked/woken 启动...但是按顺序而不是一次全部?如果是这样,可以做些什么来一次完成?

3) 如果我只关心线程在满足条件之前休眠,而不关心任何互斥量获取的任何一位,我该怎么办?是否有替代方案或当前的 std::condition_variable::wait(...) 方法应该围绕此进行黑客攻击?

如果要使用 "hackery",此函数是否可以解除 所有 等待线程的阻塞,是否可以从任何(每个线程)线程调用:

//declared somehwere and modified before sending "notify"(ies)
std::atomic<bool> global_shared_condition_atomic_bool;

//the single(for simplicity in our case) condition variable matched with the above boolean result
std::condition_variable global_shared_condition_variable;

static void MyClass:wait()
{
    std::mutex mutex;
    std::unique_lock<std::mutex> lock(mutex);

    while (!global_shared_condition_atomic_bool) global_shared_condition_variable.wait(lock);
}

它会从随机 "waiting" 个线程中调用,如下所示:

void random_thread_run()
{
    while(someLoopControlValue)
    {
        //random code...
        MyClass:wait(); //wait for whatever condition the class+method is for.
        //more random code...
    }
}

编辑:

大门class

#ifndef Gate_Header
#define Gate_Header

#include <mutex>
#include <condition_variable>

class Gate
{
public:
    Gate()
    {
        gate_open = false;
    }

    void open()
    {
        m.lock();
        gate_open = true;
        m.unlock();

        cv.notify_all();
    }

    void wait()
    {
        std::unique_lock<std::mutex> lock(m);

        while (!gate_open) cv.wait(lock);
    }

    void close()
    {
        m.lock();
        gate_open = false;
        m.unlock();
    }

private:
    std::mutex m;
    std::condition_variable cv;
    bool gate_open;
};

#endif

条件变量虚假唤醒。

必须有一个互斥量并且它必须保护某种消息才能使它们工作,否则你对它的保证为零发生任何此类唤醒。

之所以这样做,大概是因为 non-spurious 版本的有效实施最终以这种虚假版本的形式实施。

如果您无法使用互斥锁来保护消息编辑(即,没有对其进行同步,则消息的状态是未定义的行为。这可能会导致编译器优化内存读取以在第一次读取后跳过它.

即使排除未定义的行为(假设您使用原子),也存在设置消息的竞争条件,发生通知,如果您未能获得互斥体,等待通知的任何人都看不到正在设置的消息在设置变量和通知条件变量之间的时间。

除非极端情况,否则您通常希望使用 wait 的 lambda 版本。

除非您同时审计通知代码和等待代码,否则无法审计条件变量代码。

struct gate {
  bool gate_open = false;
  mutable std::condition_variable cv;
  mutable std::mutex m;

  void open_gate() {
    std::unique_lock<std::mutex> lock(m);
    gate_open=true;
    cv.notify_all();
  }
  void wait_at_gate() const {
    std::unique_lock<std::mutex> lock(m);
    cv.wait( lock, [this]{ return gate_open; } );
  }
};

  void open_gate() {
    {
      std::unique_lock<std::mutex> lock(m);
      gate_open=true;
    }
    cv.notify_all();
  }

不,您的代码将不起作用。

mutex 保护对共享变量的修改。因此,所有等待线程和信号线程 必须 锁定特定 mutex 实例。使用您所写的内容,每个线程都有自己的 mutex 个实例。

所有这些 mutex 东西的主要原因是由于 spurious wakeup 的概念,这是 OS 条件变量实现的一个不幸方面。等待它们的线程有时会启动 运行,即使条件尚未满足。

实际变量的 mutex 绑定检查允许线程测试它是否被虚假唤醒。

wait 自动释放 mutex 并开始等待条件。当 wait 退出时,mutex 作为唤醒过程的一部分被自动重新获取。现在,考虑虚假唤醒和通知线程之间的竞争。通知线程可以处于两种状态之一:即将修改变量,或者修改后即将通知所有人唤醒。

如果在通知线程即将修改变量时发生虚假唤醒,那么其中一个将首先到达 mutex。因此,虚假唤醒的线程要么看到旧值,要么看到新值。如果它看到新的,那么它已收到通知并将继续开展业务。如果它看到旧的,那么它将再次等待条件。但是如果它看到旧的,那么它会阻止通知线程修改那个变量,所以它必须等到虚假线程重新进入睡眠状态。

Why does std::condition_variable::wait(...) locks the mutex again after a "notify" has been sent to un-sleep it?

因为mutex 锁定了对条件变量的访问。从 wait 调用中醒来后,您要做的第一件事就是检查条件变量。因此,必须在 mutex.

的保护下进行

必须防止发送信号的线程在其他线程读取变量时修改它。这就是 mutex 的用途。

Seeing the behaviour in "1)", does that mean that when you do std::condition_variable::notify_all it only makes it so that all of the waiting threads are unblocked/woken up... but in order instead of all at once?

未指定他们醒来的顺序。但是,到notify_allreturns时,保证所有线程都已经畅通了。

If I only care about threads sleeping until a condition is met and not care a single bit for any mutex acquisition, what can I do?

没有。 condition_variable 要求 通过 mutex.

控制对您正在检查的实际变量的访问