使用std :: conditional_variable在条件上等待

时间:2017-01-20 18:13:02

标签: c++ multithreading c++11 blocking condition-variable

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

1)为什么std::condition_variable::wait(...)在发送“通知”以取消睡眠后再次锁定互斥锁?

2)看到“1)”中的行为,这是否意味着当你执行std::condition_variable::notify_all时,它只会使所有等待线程被解锁/唤醒...但是 in订单而不是一次全部?如果是这样,可以做些什么来一次完成所有工作?

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);
}

它会被随机的“等待”线程调用,如下所示:

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

修改

门类

#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

2 个答案:

答案 0 :(得分:8)

条件变量虚假地唤醒。

必须拥有一个互斥锁并且必须保护某种类型的消息才能使它们工作,或者你无法保证发生任何此类唤醒。

大概是这样做了,因为非虚假版本的有效实现无论如何最终会以这种虚假版本的形式实现。

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

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

除极端情况外,您通常希望使用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();
  }

答案 1 :(得分:3)

不,您的代码无效。

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

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

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

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

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

  

为什么std :: condition_variable :: wait(...)在&#34; notify&#34;之后再次锁定互斥锁。已被送去睡觉吗?

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

当其他线程正在读取变量时,必须阻止信令线程修改变量。这就是mutex的用途。

  

看到&#34; 1)&#34;中的行为,这是否意味着当你执行std :: condition_variable :: notify_all时,它只会使所有等待的线程被解除阻塞/唤醒。但是为了而不是一次性而不是全部?

未指定他们醒来的顺序。但是,在notify_all返回的时间内,所有线程都保证已被解除阻塞。

  

如果我只关心线程在满足条件之前就已经睡眠并且不关心任何互斥锁获取,我该怎么办?

无。 condition_variable 要求通过mutex来控制您检查的实际变量。