引:由上一篇mutex的介绍,基本可知通过设置mutex锁可以解决不同线程修改共享变量的线程安全问题。然而,有些时候并不止要求不同线程间访存的数据安全,而且需要各线程按照某一顺序进行访存或执行(条件变量是并发程序设计中的一种控制结构),这种情况就需要通过设置condition variable(条件变量)实现。条件变量的一般用法是:线程 A 等待某个条件并挂起,直到线程 B 设置了这个条件,并通知条件变量,然后线程 A 被唤醒。经典的 「生产者-消费者」 问题就可以用条件变量来解决。 note1 :条件变量的等待函数wait(lck) 需要与一个互斥锁搭配使用 ,要明白搭配使用的机理,否则理解上会造成混乱:在调用wait时:
首先该线程需要获取到保护v的锁 进入临界取,因为 v 应该是多个线程可以访问的。 在wait函数执行如下操作:
wait函数内部首先 mu.unlock() 释放锁 ; 然后进入等待; 如果被唤醒,则调用mu.lock() 再次获取锁。 note2 :condition_variable类更多成员函数含义和用法可见 .实例
#include<iostream>
#include<string>
#include<thread>
#include<mutex>
#include<condition_variable>
std::mutex mutex;
std::condition_variable cv;
std::string data;
bool ready = false;//条件
bool processed = false;//条件
void Worker()
{
std::unique_lock<std::mutex> lock(mutex);
//等待主线程发送数据
cv.wait(lock, []() {return ready; });
// 等待后,继续拥有锁。
std::cout << "工作线程正在处理数据" << std::endl;
// 睡眠一秒以模拟数据处理。
std::this_thread::sleep_for(std::chrono::seconds(1));
data += "已处理";
processed = true;
std::cout << "工作线程通知数据已经处理完毕" << std::endl;
// 通知前,手动解锁以防正在等待的线程被唤醒后又立即被阻塞。
lock.unlock();
cv.notify_one();
}
int main()
{
std::thread worker(Worker);
{
std::lock_guard<std::mutex> lock(mutex);
std::cout << "主线程正在准备数据..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
data = "样本数据";
ready = true;
std::cout << "主线程通知数据已经准备完毕" << std::endl;
}
cv.notify_one();
{
std::unique_lock<std::mutex> lock(mutex);
cv.wait(lock, [] {return processed; });
}
std::cout << "回到主线程, 数据 = " << data << std::endl;
worker.join();
system("pause");
return 0;
}
代码说明:
与条件变量搭配使用的「锁」,必须是 unique_lock,不能用 lock_guard(unique_lock锁机制更加灵活,可以再需要的时候进行lock或者unlock调用,而lock_guard不行),通过上边wait函数的原理即可知为什么只能是unique_lock而不能是lock_guard。 条件变量被通知后,挂起的线程就被唤醒,但是唤醒也有可能是假唤醒,或者是因为超时等异常情况,所以被唤醒的线程仍要检查条件是否满足,所以 wait 是放在条件循环里面。cv.wait(lock, [] { return ready; }); 相当于:while (!ready) { cv.wait(lock); }。(condition_variable各成员函数详细见 ) 请注意理解main函数中两个大括号{}使用的意义(变量作用域与对象析构)