条件变量能阻塞线程(wait),直到收到另一个线程发出的通知或超时,才会唤醒当前阻塞的线程。
C++11提供了两种条件变量:
(1)condition_variable,wait函数需要与std::unique_lock<std::mutex>配合使用
(2)conditon_vatiable_any,可和任意mutex配合使用,更灵活,但效率较低
条件变量的使用过程:
(1)拥有条件变量的线程获取互斥量
(2)循环检查某个条件,如果条件不满足,则阻塞直到条件满足;如果条件满足,则向下执行。
(3)某线程满足条件后,调用notify_one或notify_all唤醒一个或所有的等待线程。
可用条件变量来实现一个同步队列,同步队列作为一个线程安全的数据共享区,用于线程之间读取数据。
有限长度同步队列的实现(condition_variable_any):
#include <mutex>
#include <thread>
#include <condition_variable>
template<typename T>
class SyncQueue
{
bool IsFull() const
{
return m_queue.size() == m_maxSize;
}
bool IsEmpty() const
{
return m_queue.empty();
}
public:
SyncQueue(int maxSize):m_maxSize(maxSize)
{
}
void Put(const T& x)
{
std::lock_guard<std::mutex> locker(m_mutex);
while (IsFull())
{
cout << "缓冲区满了,需要等待..." << endl;
m_notFull.wait(m_mutex);
}
m_queue.push_back(x);
m_notEmpty.notifyone();
}
void Take(T& x)
{
std::lock_guard<std::mutex> locker(m_mutex);
while (IsEmpty())
{
cout << "缓冲区空了,需要等待..." << endl;
m_notEmpty.wait(m_mutex);
}
x = m_queue.front();
m_queue.pop_front();
m_notFull.notify_one();
}
// below three func,just for public func
bool Empty()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.empty();
}
bool Full()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.size() == m_maxSize;
}
size_t Size()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.size();
}
private:
std::list<T> m_queue;
std::mutex m_mutex;
// 队列不为空的条件变量
std::condition_variable_any m_notEmpty;
// 队列没有满的条件变量
std::condition_variable_any m_notFull;
// 同步队列最大长度
int m_maxSize;
};
这个同步队列在没有满的时候可以插入数据,如果队列满了,生产线程会在m_notFull.wait阻塞等待(wait中释放锁),等消费线程取出数据后发送notify_one通知,则生产线程被唤醒继续执行;如果队列空了,消费线程会在m_notEmpty.wati阻塞等待(wait中释放锁),等生产线程插入数据后发送notify_one通知,则消费线程被唤醒继续执行。
条件变量的wait函数还有一个重载方法,可以接受一个条件:
std::lock_guard<std::mutex> locker(m_mutex);
while (IsFull())
{
m_notFull.wait(m_mutex);
}
可以改写为:
std::lock_guard<std::mutex> locker(m_mutex);
m_notFull.wait(locker, [this]{return !IsFull();});
解释下Take中锁的状态:
首先lock_guard初始化时获取锁。如果队列满了,生产线程的m_notFull.wait会释放锁,然后阻塞。等消费线程发送了notify_one通知后,生产线程被唤醒后会重新获取锁,继续执行。直到Take函数执行完成,locker生命周期结束,再次释放锁。
std::unique_lock和std::lock_guard的差别在于前者可以自由地释放mutex,后者需要等到std::lock_guard变量生命周期结束时才能释放。
无限长度的同步队列(condition_variable,unique_lock):
#include <thread>
#include <condition_variable>
#include <mutex>
#include <list>
#include <iostream>
using namespace std;
template<typename T>
class SimpleSyncQueue
{
public:
SimpleSyncQueue()
{
}
void Put(const T& x)
{
std::lock_guard<std::mutex> locker(m_mutex);
m_queue.push_back(x);
m_notEmpty.notify_one();
}
void Take(T& x)
{
std::unique_lock<std::mutex> locker(m_mutex);
// 如果条件不满足(return false),即队列为空,继续等待wait
m_notEmpty.wait(locker, [this]{return !m_queue.empty();});
x = m_queue.front();
m_queue.pop_front();
}
// below 2 funcs just public funcs
bool Empty()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.empty();
}
size_t Size()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.size();
}
private:
std::list<T> m_queue;
std::mutex m_mutex;
// 队列不空的条件变量
std::condition_variable m_notEmpty;
};