在之前的文章中,我们已经讲过了很多种线程同步的方法,如互斥锁,信号量,读写锁等,今天我们再来学习一种线程同步的方法,条件变量。
条件变量允许一个线程通知其他的线程它们所等待的某个条件已经满足了,可以继续运行了。一个或多个线程可以在同一个条件变量上等待。当条件满足时,我们可以调用wakeOne()从所有等待在该条件变量上的线程中随机的唤醒一个线程继续运行,也可以使用wakeAll()方法同时唤醒所有等待在该条件变量上的线程。
另外,QWaitCondition和QSemaphore一样,要和QMutex配合使用,因为要访问到共享资源。
下面,我们使用常见的生产者-消费者问题来演示一下条件变量的使用。
新建一个Qt控制台程序,再新建两个线程类Producer和Consumer,继承自QThread类。
我们先在main.cpp中,声明我们要用到的全局变量。代码如下:
#include <QCoreApplication>
#include <QWaitCondition>
#include <QMutex>
#include <QQueue>
QQueue<int> buffer;
QMutex mutex;
QWaitCondition fullCond;
QWaitCondition emptyCond;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
return a.exec();
}
其中,buffer就是我们的缓冲区,mutex是保护缓存区的互斥锁,条件变量fullCond是用来等待缓冲区变满(有数据),条件变量emptyCond是用来等待缓冲区变为空。
下面来实现我们的消费者和生产者线程:
生产者代码如下:
void Producer::run()
{
while(true)
{
mutex.lock();
while(buffer.size() >= 10)
{
emptyCond.wait(&mutex);
}
int num = rand();
buffer.enqueue(num);
qDebug() << "enqueue: " << num;
mutex.unlock();
fullCond.wakeAll();
}
}
此处,我们假定缓冲区最多存储10个元素。所以,我们先判断缓冲区是否已满,如果已满,则等待其变为空,否则,产生一个随机数放入队列中,最后通知消费者线程,可以进行消费。
消费者代码如下:
void Consumer::run()
{
while(true)
{
mutex.lock();
while(buffer.size() <= 0)
{
fullCond.wait(&mutex);
}
qDebug() << "dequeue: " << buffer.dequeue();
mutex.unlock();
emptyCond.wakeAll();
}
}
对于消费者来说,要先判断缓冲区是否有数据可消费,如果没有,则等待其生产者生产出新的数据,如果有,则消费一个数据,最后,通知生产者继续生产。
运行结果如下:
可以看出,几乎是生产者生产一个数据,消费者就消费一个。如果想看到累积的效果,可以使用线程睡眠来实现,大家可以自行测试。