条件变量之虚假唤醒

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/shizheng163/article/details/83661861

如有错误请及时指正!

引言

当我们使用互斥量(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;
}
  • 当消费者的数量(CONSUMER_NUM)为1时没有其他线程竞争队列,不会触发虚假唤醒。
  • 当生产者的数量(PRODUCTER_NUM)为1时,
    • 当使用notify_one通知消费线程时,不会发生虚假唤醒,因为每次只会有一个消费者线程收到信号被唤醒,在产品被消耗掉之前不会有新的信号发出来。
    • 当使用notify_all通知消费线程时,会发生虚假唤醒,会有多个消费者线程收到信号被唤醒,当一个线程被唤醒之前,可能其他线程先被唤醒先持有锁,将产品消耗掉。
  • 当生产者的数量(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();
    }
}

被唤醒后再次判断,若无数据可处理,则应继续休眠。


作者:shizheng163
来源:CSDN
原文:https://blog.csdn.net/shizheng163/article/details/83661861
版权声明:本文为博主原创文章,转载请附上博文链接!


猜你喜欢

转载自blog.csdn.net/shizheng163/article/details/83661861