条件变量 std::condition_variable 以及成员函数

条件变量condition_variable

std::condition_variable实际上是一个类,是一个和条件相关的类,说白了就是等待一个条件达成。

1
std::condition_variable my_cond;

wait()

wait()第一个参数为std::unique_lock<std::mutex>,第二个参数为可选参数【可调用对象如 lambda 表达式 或者 函数】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class A {
public:
// 收集玩家命令线程
void inMsgRecvQueue()
{
for(int i = 0 ; i < 10000 ; i++)
{
std::cout << "inMsgRecvQueue() insert " << i << std::endl;
std::unique_lock<std::mutex> sb(my_mutex1);

msgRecvQueue.emplace_back(i);
my_cond.notify_all(); // 尝试把 outMsgRecvQueue() 中的 wait() 唤醒
}
return;
}
// 取出命令线程
void outMsgRecvQueue()
{
int command = 0;
while(true)
{
std::unique_lock<std::mutex> sb(my_mutex1);
my_cond.wait(sb,[this] {
if(!msgRecvQueue.empty())
return true;
return false;
});
// 一旦走到这 队列中一定会有数据
command = msgRecvQueue.front();
msgRecvQueue.pop_front();
sb.unlock(); // 提前 unlock ,以免影响效率
cout << "thread ID = " << this_thread::get_id() << " outMsgRecvQueue Start And Get Value! = " << command << endl;

}// end while


cout << "end!\n";
}
private:
// 用于收集玩家发送过来的命令
std::list<int> msgRecvQueue;
std::mutex my_mutex1; // 创建一个互斥量
std::condition_variable my_cond; // 生成一个条件变量对象
};

  1. 如果有第二个参数

    • 如果第二个参数返回值为 false 那么 wait() 将解锁互斥量,并堵塞到本行,堵塞到其他某个线程调用 notify_one() 或者 notify_all() 成员函数为止

    • 如果第二个参数返回值为 true 那么 wait() 将重新解锁上互斥量,并继续执行之后的代码

  2. 如果没有第二个参数

    • 就与第二个参数为 false 效果相同,将解锁互斥量,并堵塞到本行,堵塞到其他某个线程调用 notify_one() 或者 notify_all() 成员函数为止

当唤醒其他线程中的 wait()

  1. wait() 不断尝试重新获取互斥量的锁,如果获取不到 就会一直反复获取锁,如果获取到就继续执行 2.
    • 如果 wait() 有第二个参数(当前为 lambda 表达式)且返回值为 falsewait() 又对互斥量解锁并堵塞到这,等待再次被唤醒
    • 如果 wait() 有第二个参数(当前为 lambda 表达式)且返回值为 truewait() 返回,将互斥量再次上锁 流程继续(此时互斥量已经被锁住)
    • 如果 没有第二个参数 wait() 将互斥量再次上锁 直接返回,流程继续

notify_one()notify_all()

  • notify_one()唤醒一个线程中的 wait()
  • notify_all() 唤醒所有线程中的 wait()

虚假唤醒

notify_one() 或者 notify_all() 唤醒 wait() 后,实际有些线程可能不满足唤醒的条件,就会造成虚假唤醒,可以在 wait() 中再次进行判断解决虚假唤醒。
解决:wait 中要有第二个参数(lambda),并且这个 lambda 中要正确判断所处理的公共数据是否存在。

深入思考

上面的代码可能导致出现一种情况:
因为outMsgRecvQueue()inMsgRecvQueue()并不是一对一执行的,所以当程序循环执行很多次以后,可能在msgRecvQueue 中已经有了很多消息,但是,outMsgRecvQueue还是被唤醒一次只处理一条数据。这时可以考虑把outMsgRecvQueue多执行几次,或者对inMsgRecvQueue进行限流。