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管理的是已使用的仓库数量,确保消费者不会在空的缓冲区中尝试消费产品。

通过使用两个信号量,生产者和消费者可以正确协调它们之间的操作,确保系统的稳定运行。