Qt篇: 多线程信号量
信号量的概念
Qt是直接提供信号量的使用API的,信号量与QMutex锁不一样,QMutex一把锁只能锁住一条线程,而信号量是特殊的线程锁,允许N个线程同时访问临界资源。
QSemaphore
QSemaphore是Qt提供的信号量API,QSemaphore对象中维护一个整型值(n),这个整型值就对标着多少条线程。acquire()会使得这个n值减1,release()会将该值加1,当这个n=0时,acquire()函数会阻塞当前线程。
QSemaphore semaphore(1);
semaphore.acquire();
// 对临界资源读写操作
semaphore.release();
其实这段代码用QMutex来表示,代码如下所示,这两段代码是等价的,喜欢用哪种,取决于个人喜好,哪个方便哪个来。
QMutex mutex;
mutex.lock();
// 对临界资源读写操作
mutex.unlock();
QSemaphore示例
-
场景:有n个仓库,x个生产者,y个消费者,生产者生成商品存放到空着的仓库中,注意前提是仓库空着的,消费者则从仓库中取出商品,前提是仓库不能为空。
-
场景分析:仓库即为临界资源,有x个仓库即允许有N个生产者来存放商品,也允许y个消费者来取商品,这x和y就对应着多条线程来对这个临界资源进行读写操作。
#include <QApplication>
#include <QThread>
#include <QDebug>
#include <QSemaphore>
// 仓库个数
const int SIZE = 6;
// g_buff[i]表示对应的仓库
unsigned char g_buff[SIZE];
// 空闲仓库的信号量
QSemaphore g_free_sem(SIZE);
// 已使用的仓库信号量
QSemaphore g_used_sem(0);
// 生产者线程
class Producter : public QThread {
protected:
void run() {
while (true) {
int productValue = qrand() % 10;
g_free_sem.acquire();
for(int i; i<SIZE; i++){
if(!g_buff[i]) {
g_buff[i] = productValue;
qDebug() << objectName() << "generate: ( " << i << ", " << productValue << " )";
break;
}
}
g_used_sem.release();
}
}
};
// 消费者线程
class Customer : public QThread {
protected:
void run() {
while (true) {
g_used_sem.acquire();
for (int i = 0; i<SIZE; i++) {
if(g_buff[i]) {
int productValue = g_buff[i];
g_buff[i] = 0;
qDebug() << objectName() << "consume: (" << i << ", " << productValue << " )";
break;
}
}
g_free_sem.release();
}
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
qDebug() << "main begin...";
// 4个生产者
Producter p1;
p1.setObjectName("p1");
Producter p2;
p2.setObjectName("p2");
Producter p3;
p3.setObjectName("p3");
Producter p4;
p4.setObjectName("p4");
// 3个消费者
Customer c1;
c1.setObjectName("c1");
Customer c2;
c2.setObjectName("c2");
Customer c3;
c3.setObjectName("c3");
p1.start();
p2.start();
p3.start();
c1.start();
c2.start();
c3.start();
qDebug() << "main end...";
return a.exec();
}
从结果来看,4个生产者不断地给空的仓库存放商品,3个消费者不断地从仓库中取出商品,每个仓库都是有秩序的进行着,QSemaphore信号量保证了高并发性能,让线程实现最大化的工作。那么为什么使用两个信号量来表示呢?
在生产者-消费者模型中,生产者和消费者要协调访问共享缓冲区(仓库)。为了防止竞争条件和确保同步,两个信号量各自负责协调不同的资源状态:
- g_free_sem管理的是空闲的仓库数量,确保生产者不在满的缓冲区中添加产品。
- g_used_sem管理的是已使用的仓库数量,确保消费者不会在空的缓冲区中尝试消费产品。
通过使用两个信号量,生产者和消费者可以正确协调它们之间的操作,确保系统的稳定运行。