在Qt中QMutex提供了一个互斥锁,在任何时间至多有一个线程可以获得mutex。如果一个线程尝试获得mutex,而此时mutex已经被锁住了,则这个线程将会睡眠,直到现在获得mutex的线程对mutex进行解锁为止。互斥锁可以用于对共享数据的访问进行保护。
而QSemaphore表示信号量,是QMutex的一般化,它与互斥锁mutex不同的地方在于:QSemaphore可以保护一定数量的相同的资源,而互斥锁mutex只能保护一个资源。
关于信号量的使用有个很典型的例子:解决生产者-消费者问题。这个例子展示了如何使用QSemaphore信号量来保护对生产者线程和消费者线程共享的环形缓冲区的访问。
程序主要通过Producer生产者类以及Consumer消费者类构成,整体代码如下:
#include<QtCore>
#include<stdio.h>
#include<stdlib.h>
#include<QDebug>
const int DataSize = 10; //生产者将要产生数据的数量大小
const int BufferSize = 5; //环形缓冲区的大小
char buffer[BufferSize]; //缓冲区
QSemaphore freeBytes(BufferSize); //该信号量控制缓冲区的空闲区域
QSemaphore usedBytes; //该信号量控制缓冲区的已使用区域
class Producer:public QThread
{
public:
void run(); //生产者线程
};
void Producer::run()
{
qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
for(int i = 0; i < DataSize; ++ i){
freeBytes.acquire(); //默认获取一个freeBytes信号量字节
buffer[i % BufferSize] = "ACGT"[(int)qrand() % 4]; //随机获取其中一个字符
qDebug()<<QString("producer:%1").arg(buffer[i % BufferSize]);
usedBytes.release(); //释放usedBytes信号量的一个字节
}
}
class Consumer : public QThread
{
public:
void run(); //消费者线程
};
void Consumer::run() //与生产者线程类似
{
for(int i = 0; i < DataSize; ++ i){
usedBytes.acquire();
qDebug()<<QString("consumer:%1").arg(buffer[i%BufferSize]);
freeBytes.release();
}
}
int main(int argc,char * argv[])
{
QCoreApplication app(argc,argv);
Producer producer;
Consumer consumer;
//启动生产者、消费者线程
producer.start();
consumer.start();
//确保在程序退出前这两个线程有时间执行完毕
producer.wait();
consumer.wait();
return app.exec();
}
在这个程序中,生产者线程向缓冲区写入数据,直到它到达缓冲区的终点,这时它会从起点重新开始,覆盖已经存在的数据。消费者线程读取产生的数据,并将其输出。
其中freeBytes信号量控制缓冲区的空闲区域,即生产者线程还没有添加数据或者消费者已经进行了数据读取的区域;usedBytes信号量控制已经使用了的缓冲区区域,即生产者线程已经添加数据但消费者线程还未进行读取的区域。
这两个信号量一起保证了生产者线程永远不会在消费者线程前多BufferSize字节,而消费者线程永远不会读取生产者还没有生产的数据。
注意:在对于消费者线程读取数据还是生产者线程继续产生数据时,两个线程的执行顺序是无法确定的,简单来说,在每次重新运行该程序时,输出的结果无法保证与先前运行的结果一致。