多线程之虚假唤醒
首先介绍几个函数,是多线程中关于条件变量的:
pthread_cond_wait
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
//成功,返回0; 失败,返回错误编号
该函数的主要作用是,将线程放置等待线程队列上,然后阻塞,等待条件发生唤醒线程。其执行步骤如下:
- 利用 mutex 对 cond 加锁(在函数外部调用);
- 进入函数;
- 将该线程放至条件等待线程队列上;
- 利用 mutex 对 cond 解锁;
- 阻塞(等待);
- 被唤醒,函数返回时,利用 mutex 对 cond 进行加锁。
pthread_cond_signal及pthread_cond_broadcast
int pthread_cond_signal(pthread_cond_t* cond);
int pthread_cond_broadcast(pthread_cond_t* cond);
// 成功,返回0; 失败,返回错误编号
通知线程条件已经满足,从而唤醒线程。pthread_cond_signal至少唤醒一个线程,而pthread_cond_broadcast可以唤醒所有等待该条件的线程。
虚假唤醒的概念:多个线程同时等待同一个条件满足时,当pthread_cond_signal唤醒线程时,可能会唤醒多个线程,但是如果对应的资源只有一个线程能获得,其余线程就无法获得该资源,因此其余线程的唤醒是无意义的(有时甚至是有危害的),其余线程的唤醒则被称为虚假唤醒。
举例:1个生产者,2个消费者
- 初始情况下,由于生产者没有工作,因此队列为空,线程 1 和 线程 2 等待;
- 生产者开始工作,产生一个元素入队,并唤醒 线程 1 和 线程2;
- 假设 线程 1 获取到队列元素,则 线程 2 的唤醒毫无意义,如果处理不当,还会造成程序错误。
根据以上例子,可以编写如下代码:
#include <pthread.h>
struct Message
{
Message* next;
// other data
};
Message* workQueue;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* processMessage(void*) // 消费者(消费者 1 和 消费者 2 相同)
{
Message* messagePtr;
while(true)
{
pthread_mutex_lock(&mutex);
while (workQueue == nullptr)
{
pthread_cond_wait(&cond, &mutex);
}
messagePtr = workQueue;
workQueue = workQueue->next;
pthread_mutex_unlock(&mutex);
// process the message
}
return nullptr;
void* enqueueMessage(Message* messagePtr) // 生产者
{
pthread_mutex_lock(&mutex);
messagePtr->next = workQueue;
workQueue = messagePtr;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
}
生产者产生一个消息元素时,消费者1 和 消费者 2 同时被唤醒,假设消费者 1 获得该资源,则消费者 2 被虚假唤醒,产生无意义行为后继续阻塞等待。倘若以下代码中的while换成if,则会造成更严重的危害:
while (workQueue == nullptr) // if (workQueue == nullptr)
{
pthread_cond_wait(&cond, &mutex);
}
消费者 2 被唤醒后,不会再次队列中是否有消息元素,从而可能对空的消息队列进行操作,造成难以估计的错误。而采用while则可以避免该问题的发生。
本文探讨了C/C++多线程中的条件变量使用,特别是虚假唤醒的问题。当唤醒线程时,可能会导致多个线程被不必要的唤醒,这被称为虚假唤醒。以1个生产者和2个消费者的例子说明,如果不正确处理,虚假唤醒可能导致程序错误。在处理条件变量时,使用`std::unique_lock::unlock_and_dispose`可以避免此类问题。

7679

被折叠的 条评论
为什么被折叠?



