【QT 之 QSemaphore】信号量

QSemaphore 信号量

信号量是互斥锁的一种推广。虽然互斥锁只能锁定一次,但也可以多次获取信号量。信号量通常用于保护一定数量的相同资源。
信号量支持两种基本的操作,acquire() 和 release().acquire(n)尝试获取n个资源,如果没有那么多可用资源,那么调用将被阻止,直到出现这种情况(可以获取到n个资源)。release(n) 释放n个资源。
tryAcquire() 函数如果不能获取到资源则立即返回 ,available() 函数随时都会返回可获得资源的数量。
官方案例:

QSemaphore sem(5);      // sem.available() == 5

sem.acquire(3);         // sem.available() == 2
sem.acquire(2);         // sem.available() == 0
sem.release(5);         // sem.available() == 5
sem.release(5);         // sem.available() == 10

sem.tryAcquire(1);      // sem.available() == 9, returns true
sem.tryAcquire(250);    // sem.available() == 9, returns false

信号量的一个典型应用程序是控制对生产者线程和消费者线程共享的循环缓冲区的访问。
例如:信号量的一个非计算示例是在餐厅用餐。信号量是用餐厅里的椅子数量初始化的。当人们到达时,他们想要一个座位。当座位坐满时,available()递减。当人们离开时,available()会增加,允许更多的人进入。如果一个10人的聚会想要就座,但只有9个座位,那10个人会等待,但一个4人的聚会会就座(将可用座位增加到5个,使10人的派对等待的时间更长)。

成员函数

QSemaphore::QSemaphore(int n = 0)
//创建新的信号量并且初始化资源的数量,资源视为n,默认为0
void QSemaphore::acquire(int n = 1)
//尝试获取信号量保护的n个资源。如果n>available(),则此调用将阻塞,直到有足够的资源可用为止。
int QSemaphore::available() const
//返回信号量当前可用的资源数。这个数字永远不能是负数。
void QSemaphore::release(int n = 1)
//释放n个信号量保护的资源
bool QSemaphore::tryAcquire(int n = 1)
//尝试获取n个信号量保护的资源,如果获取到返回为true,
//如果 available() < n,则立即返回false,不会调用任何资源
bool QSemaphore::tryAcquire(int n, int timeout)
//尝试获取n个信号量保护的资源,如果获取到返回为true,
//如果available()<n,则此调用最多将等待超时毫秒,以便资源变为可用。
//注意:传递一个负数作为超时相当于调用acquire(),即如果超时为负数,此函数将永远等待资源可用。

QSemaphoreRelaser是围绕此函数的RAII包装器。

信号量的案例

生产者将数据写入缓冲区,直到数据到达缓冲区的末尾,然后从头开始重新启动,覆盖现有数据。使用者线程在生成数据时读取数据,并将其写入标准错误。
信号量使并发级别高于互斥量成为可能。如果对缓冲区的访问由QMutex保护,则使用者线程不能与生产者线程同时访问缓冲区。然而,让两个线程同时在缓冲区的不同部分工作并没有坏处。
该示例包括两个类:生产者和消费者。两者都继承自QThread。用于在这两个类之间通信的循环缓冲区和保护它的信号量是全局变量。

从查看循环缓冲区和相关的信号量开始:

const int DataSize = 100000;//生产者生成数据量,使其简单设为常数

const int BufferSize = 8192;//循环缓冲区的大小,这意味着在某个时刻生产者将到达缓冲区的末尾并从头开始。
char buffer[BufferSize];
//为了同步生产者和消费者,我们需要两个信号量。
//freeBytes信号量控制缓冲区的“空闲”区域(生产者尚未填充数据或消费者已经读取的区域)。
//usedBytes信号量控制缓冲区的“已用”区域(生产者已填充但消费者尚未读取的区域)。
QSemaphore freeBytes(BufferSize);
QSemaphore usedBytes;
//信号量一起确保生产者永远不会领先消费者超过BufferSize字节,消费者永远不会读取生产者尚未生成的数据。
//freeBytes信号量是用BufferSize初始化的,因为最初整个缓冲区都是空的。usedBytes信号量初始化为0(如果未指定,则为默认值)。

生产者类

class Producer : public QThread
{
public:
    void run() override
    {
        for (int i = 0; i < DataSize; ++i) {
            freeBytes.acquire();
            buffer[i % BufferSize] = "ACGT"[QRandomGenerator::global()->bounded(4)];
            usedBytes.release();
        }
    }
};

生产者生成DataSize字节的数据。在将字节写入循环缓冲区之前,它必须使用freeBytes信号量获取一个“空闲”字节。如果消费者没有跟上生产者的步伐,那么QSemaphore::acquire()调用可能会被阻塞。
最后,生产者使用usedBytes信号量释放一个字节。“空闲”字节已成功转换为“已使用”字节,可供消费者读取。

消费者类

class Consumer : public QThread
{
    Q_OBJECT
public:
    void run() override
    {
        for (int i = 0; i < DataSize; ++i) {
            usedBytes.acquire();
            fprintf(stderr, "%c", buffer[i % BufferSize]);
            freeBytes.release();
        }
        fprintf(stderr, "\n");
    }
};

代码与生产者非常相似,只是这次我们获取了一个“已使用”的字节,并释放了一个”空闲“的字节,而不是相反。

主函数

//在main()中,我们创建两个线程,并调用QThread::wait()以确保两个线程在退出之前都有时间完成:
int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    Producer producer;
    Consumer consumer;
    producer.start();
    consumer.start();
    producer.wait();
    consumer.wait();
    return 0;
}

那么,当我们运行程序时会发生什么呢?最初,生产者线程是唯一一个可以做任何事情的线程;消费者被阻止等待usedBytes信号量释放(其初始可用()计数为0)。一旦生产者在缓冲区中放入一个字节,freeBytes.available()为BufferSize-1usedBytes.available()为1。在这一点上,可能会发生两件事:要么使用者线程接管并读取该字节,要么生产者线程生成第二个字节。
此示例中提供的生产者-消费者模型使编写高度并发的多线程应用程序成为可能。在多处理器机器上,该程序的速度可能是等效的基于互斥量的程序的两倍,因为两个线程可以在缓冲区的不同部分同时处于活动状态。
但要注意,这些好处并不总是能实现的。获取和发布QSemaphore是有成本的。在实践中,将缓冲区划分为块并对块而不是单个字节进行操作可能是值得的。缓冲区大小也是一个必须根据实验仔细选择的参数。

  • 8
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值