网站首页 > 教程分享 正文
引言
当我们使用互斥量(Mutex)与条件变量(condition_variable)进行多线程同步时有可能会产生虚假唤醒现象, 那么究竟什么是虚假唤醒,它会在什么情况下被触发,什么情况下无需考虑虚假唤醒,如何避免?
1.什么是虚假唤醒?
Linux帮助中有提到
在多核处理器下,pthread_cond_signal可能会激活多于一个线程(阻塞在条件变量上的线程)。结果是,当一个线程调用pthread_cond_signal()后,多个调用pthread_cond_wait()或pthread_cond_timedwait()的线程返回。这种效应成为”虚假唤醒”(spurious wakeup)
通俗点的解释就是:
你(消费线程)收到了其他线程(生产)传来的唤醒信号,但是你唤醒后发现别的消费线程处理的比你快,此时没有数据被可以用于操作,这种情况的发生是预期之外的,称之为虚假唤醒。
2.什么情况下会发生虚假唤醒
以以下C++代码举例。
#include <iostream>
#include <condition_variable>
#include <mutex>
#include <thread>
#include <unistd.h>
#include <assert.h>
#include <queue>
using namespace std;
#define PRODUCTER_NUM 10
#define CONSUMER_NUM 100
std::mutex mutexForProduct;
static queue<unsigned> queueForProduct;
unsigned uProductTotalNum = 0;
condition_variable condVal;
//生产线程
void ConsumerFunction(unsigned threadIndex)
{
while(1)
{
unique_lock<mutex> locker(mutexForProduct);
if(queueForProduct.empty())
{
condVal.wait(locker);
}
if(queueForProduct.empty())
{
printf("Consumer[%02u] Want To Buy Product, But There is not remain Product", threadIndex);
assert(false);
}
unsigned productIndex = queueForProduct.front();
printf("Consumer[%02u] Buying Product[%06u], RemainSize= %lu\n", threadIndex, productIndex, queueForProduct.size());
queueForProduct.pop();
}
}
//消费线程
void ProducerFunction()
{
while(1)
{
usleep(100 * 1000);
unique_lock<mutex> locker(mutexForProduct);
queueForProduct.push(++uProductTotalNum);
condVal.notify_one();
}
}
int main()
{
for(unsigned i = 0; i < CONSUMER_NUM; i++)
thread(&ConsumerFunction, i).detach();
for(unsigned i = 0; i < PRODUCTER_NUM; i++)
thread(&ProducerFunction).detach();
sleep(3600);
return 0;
}
(1) 当消费者的数量(CONSUMER_NUM)为1时没有其他线程竞争队列,不会触发虚假唤醒。
(2) 当生产者的数量(PRODUCTER_NUM)为1时,
当使用notify_one通知消费线程时,不会发生虚假唤醒,因为每次只会有一个消费者线程收到信号被唤醒,在产品被消耗掉之前不会有新的信号发出来。
当使用notify_all通知消费线程时,会发生虚假唤醒,会有多个消费者线程收到信号被唤醒,当一个线程被唤醒之前,可能其他线程先被唤醒先持有锁,将产品消耗掉。
(3) 当生产者的数量(PRODUCTER_NUM)大于1时,无论是使用notify_one,或者是notify_all都会发生虚假唤醒,当多个生产者使用notify_one时,多个线程被唤醒,有可能其中一个处理的特别快,将所有的数据都处理完毕,那么接下来被唤醒的线程都无数据可处理
结论
只有在生产线程与消费线程都为多个的情况下才有可能发生虚假唤醒,只要有一方的数量为1,就不会发生!
3. 如何避免虚假唤醒
将消费者线程修改如下
void ConsumerFunction(unsigned threadIndex)
{
while(1)
{
unique_lock<mutex> locker(mutexForProduct);
while(queueForProduct.empty())
{
condVal.wait(locker);
}
if(queueForProduct.empty())
{
printf("Consumer[%02u] Want To Buy Product, But There is not remain Product", threadIndex);
assert(false);
}
unsigned productIndex = queueForProduct.front();
printf("Consumer[%02u] Buying Product[%06u], RemainSize= %lu\n", threadIndex, productIndex, queueForProduct.size());
queueForProduct.pop();
}
}
被唤醒后再次判断,若无数据可处理,则应继续休眠。
猜你喜欢
- 2024-10-18 信号 - Linux Signal - 网络编程的相关信号
- 2024-10-18 干货 | 一文搞定 pytest 自动化测试框架(一)
- 2024-10-18 linux网络编程—tcp和udp基本函数调用过程及如何选择
- 2024-10-18 C语言之结构体基础(c语言结构体经典例题)
- 2024-10-18 linux定时器编程详解(包含代码)(linux定时器执行脚本)
- 2024-10-18 C语言学习第16篇---三目运算符和逗号表达式
- 2024-10-18 Linux网络编程相关高级I/O函数 - 用于创建文件描述符的函数
- 2024-10-18 干货 | 一文搞定 pytest 自动化测试框架(二)
- 2024-10-18 现代c++之移动构造,移动赋值,拷贝构造,拷贝赋值
- 2024-10-18 探索C语言断言:保证程序的健壮性和可靠性
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- css导航条 (66)
- sqlinsert (63)
- js提交表单 (60)
- param (62)
- parentelement (65)
- jquery分享 (62)
- check约束 (64)
- curl_init (68)
- sql if语句 (69)
- import (66)
- chmod文件夹 (71)
- clearinterval (71)
- pythonrange (62)
- 数组长度 (61)
- javafx (59)
- 全局消息钩子 (64)
- sort排序 (62)
- jdbc (69)
- php网页源码 (59)
- assert h (69)
- httpclientjar (60)
- postgresql conf (59)
- winform开发 (59)
- mysql数字类型 (71)
- drawimage (61)
本文暂时没有评论,来添加一个吧(●'◡'●)