菜鸟笔记
提升您的技术认知

c 条件变量-ag真人游戏

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

std::condition_variable

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

// conditionvariable.cpp
#include 
#include 
#include 
#include 
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 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 
#include 
#include 
#include 
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 lck(mutex_);
    condvar.wait(lck,[]{return dataready;});
    dothework();
    std::cout << "work done." << std::endl;
}
void setdataready(){
    std::lock_guard 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就会发生一些奇怪的现象:

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

网站地图