C++条件变量--std::condition_variable

条件变量允许我们通过通知进而实现线程同步。
因此,您可以实现发送方/接收方或生产者/消费者之类的工作流。
在这样的工作流程中,接收者正在等待发送者的通知。如果接收者收到通知,它将继续工作。


std::condition_variable

条件变量可以履行发送者或接收者的角色。
作为发送者,它可以通知一个或多个接收者。
这就是使用条件变量所需要知道的基本所有内容,程序示例:

// conditionVariable.cpp

#include <iostream>
#include <condition_variable>
#include <mutex>
#include <thread>

std::mutex mutex_;
std::condition_variable condVar;

void doTheWork(){
  std::cout << "Processing shared data." << std::endl;
}

void waitingForWork(){
    std::cout << "Worker: Waiting for work." << std::endl;

    std::unique_lock<std::mutex> lck(mutex_);
    condVar.wait(lck);
    doTheWork();
    std::cout << "Work done." << std::endl;
}

void setDataReady(){
    std::cout << "Sender: Data is ready."  << std::endl;
    condVar.notify_one();
}

int main(){

  std::cout << std::endl;

  std::thread t1(waitingForWork);
  std::thread t2(setDataReady);

  t1.join();
  t2.join();

  std::cout << std::endl;
  
}


该程序有两个子线程: t1和t2。
它们在第33行和第34行中获得可调用的有效负载(函数或函子) waitingForWork和setDataReady。

函数setDataReady通过使用条件变量condVar调用condVar.notify_one()进行通知。
在持有锁的同时,线程T2正在等待其通知: condVar.wait(lck).
在等待的线程总是会执行相同的步骤:线程醒来->试图得到锁->检查是否持有锁,如果通知到达,并在获取锁失败的情况下,让自己回到睡眠状态;在获取锁成功的情况下,线程离开上面的循环过程并继续其工作。
该程序的输出也没什么意外,但是那是我的第一印象。可是接下来再看。。。
这里写图片描述


虚假的唤醒

细节决定成败。事实上,可能发生的是,接收方在发送方发出通知之前完成了任务。
这怎么可能呢?接收方对虚假的唤醒很敏感。所以即使没有通知发生,接收方也有可能会醒来。
为了保护它,我不得不向等待方法添加一个判断。
这就是我在下一个例子中所做的:

// conditionVariableFixed.cpp

#include <iostream>
#include <condition_variable>
#include <mutex>
#include <thread>

std::mutex mutex_;
std::condition_variable condVar;

bool dataReady;

void doTheWork(){
  std::cout << "Processing shared data." << std::endl;
}

void waitingForWork(){
    std::cout << "Worker: Waiting for work." << std::endl;

    std::unique_lock<std::mutex> lck(mutex_);
    condVar.wait(lck,[]{return dataReady;});
    doTheWork();
    std::cout << "Work done." << std::endl;
}

void setDataReady(){
    std::lock_guard<std::mutex> lck(mutex_);
    dataReady=true;
    std::cout << "Sender: Data is ready."  << std::endl;
    condVar.notify_one();
}

int main(){

  std::cout << std::endl;

  std::thread t1(waitingForWork);
  std::thread t2(setDataReady);

  t1.join();
  t2.join();

  std::cout << std::endl;
  
}


与第一个示例的关键区别是在第11行中使用了一个布尔变量dataReady 作为附加条件。
dataReady在第28行中被设置为true。

它在函数waitingForWork中被检查:

condVar.wait(lck,[]{return dataReady;})

这就是为什么wait方法有一个额外的重载,它接受一个判定。判定是个callable,它返回true或false。
在此示例中,callable是lambda函数。因此,条件变量检查两个条件:判定是否为真,通知是否发生。


关于dataReady
dataReady是个共享变量,将会被改变。所以我不得不用锁来保护它。
因为线程t2只设置和释放锁一次,所以std::lock_guard已经够用了。但是线程t1就不行了,wait方法将持续锁定和解锁互斥体。所以我需要更强大的锁:std::unique_lock。
但这还不是全部,条件变量有很多挑战,它们必须用锁来保护,并且易受虚假唤醒的影响。
大多数用例都很容易用tasks来解决,后续再说task问题。


唤醒不了

条件变量的异常行为还是有的。大约每10次执行一次conditionVariable.cpp就会发生一些奇怪的现象:
这里写图片描述

译者水平有限,大多谷歌翻译,看不懂的请看原文地址。对于部分人,我又不赚你什么钱,原文也已经附上了,你在喷什么shit?你自己贡献了什么?给对你有用的博文点过赞?

我不知道怎么回事,这种现象完全违背了我对条件变量的直觉。
在安东尼·威廉姆斯的支持下,我解开了谜团。
问题在于,如果发送方在接收方进入等待状态之前发送通知,则通知会丢失。C ++标准同时也将条件变量描述为同步机制,“condition_variable类是一个同步原语,可以用来同时阻塞一个线程或多个线程。。。”。
因此,通知消息已经丢失了,但是接收方还在等啊和等啊等啊等啊…
怎么解决这个问题呢?去除掉wait第二个参数的判定可以有效帮助唤醒。实际上,在判定设置为真的情况下,接收器也能够独立于发送者的通知进而继续其工作。


原文地址:

http://www.modernescpp.com/index.php/condition-variables
相关推荐
©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页