notify_one()与notify_all()常用来唤醒阻塞的线程,线程被唤醒后立即尝试获得锁。
notify_one()因为只唤醒一个线程,不存在锁争用,所以能够立即获得锁。其余的线程不会被唤醒,等待再次调用notify_one()或者notify_all()。
notify_all()会唤醒所有阻塞的线程,存在锁争用,只有一个线程能够获得锁。那其余未获取锁的线程接着会怎么样?会阻塞?还是继续尝试获得锁?答案是会阻塞,等待操作系统在互斥锁的状态发生改变时唤醒线程。当持有锁的线程释放锁时,操作系统会唤醒这些阻塞的线程,而这些线程会继续尝试获得锁。
下面例子说明:
#pragma once
#include <thread>
#include <condition_variable>
#include <mutex>
#include <queue>
#include <iostream>
class ThreadApply
{
public:
explicit ThreadApply();
virtual ~ThreadApply();
void setValueToQueue();
void getValueByQueue();
private:
std::condition_variable m_conditionVar;
std::mutex m_mutex;
std::queue<int> m_queue;
};
#include "ThreadApply.h"
ThreadApply::ThreadApply()
{
}
ThreadApply::~ThreadApply()
{
}
void ThreadApply::setValueToQueue()
{
while (true)
{
std::unique_lock<std::mutex> lock(m_mutex);
int value = std::rand();
std::cout << "set Value:" << value << "thread id:" << std::this_thread::get_id();
m_queue.push(value);
m_conditionVar.notify_all();
}
}
void ThreadApply::getValueByQueue()
{
while (true)
{
std::unique_lock<std::mutex> lock(m_mutex);
m_conditionVar.wait(lock, [this]()
{
if (!m_queue.empty())
return true;
else
return false;
});
int fValue = m_queue.front();
std::cout << "第一个数据:" << fValue << "thread id:" << std::this_thread::get_id();
m_queue.pop();
lock.unlock();
}
}
m_conditionVar.notify_all()被调用后,所有子线程都被唤醒,然后尝试获得锁,其中的一个线程获得锁后继续执行后面的代码,而未获得锁的线程再次进入阻塞状态,等待操作系统在当前获得锁的线程释放锁之后唤醒它们。当获得锁的线程的线程函数执行完毕释放互斥锁后,刚刚的那些处于阻塞的线程会都被唤醒,其中的一个会获得互斥锁,而其余的再次进行阻塞状态。之后循环这个过程,知道所有的子线程最终都能够获得锁而正常退出
m_conditionVar.notify_one()被调用后,只会唤醒一个线程,其余的阻塞线程由于没有被通知,所以一致会保持在阻塞状态。即使那个被唤醒的线程在线程函数执行完毕后互斥锁已被释放,其余的阻塞线程依旧不会有任何反应。
因此,线程阻塞在条件变量时,等待notify_one()或者notify_all()来唤醒。线程被唤醒后,会尝试获得锁,如果未获得锁,会重新进入阻塞状态。
注意区分线程处理阻塞状态时,是由于等待条件阻塞或是尝试获得锁而阻塞。
如果是因为等待条件变量阻塞,只能由notify_one()或者notify_all()来唤醒;
如果是为尝试获得锁而阻塞,只能由操作系统在锁的状态发生变化时唤醒;