使用信号量的生产者与消费者
“使用信号量的生产者和消费者”示例演示了如何使用QSemaphore 来控制对生产者线程和消费者线程共享的环形缓冲区的访问。
生产者将数据写入缓冲区,直到达到缓冲区末尾,此时它将从开头重新开始,覆盖现有数据。消费者线程在数据生成时读取数据,并将其写入标准错误输出。
与互斥锁相比,信号量能够实现更高层次的并发性。如果对缓冲区的访问由QMutex 进行保护,则消费者线程就无法与生产者线程同时访问缓冲区。然而,这两个线程同时处理缓冲区的不同部分并无不妥。
该示例包含两个类:Producer 和Consumer 。二者均继承自QThread 。用于这两个类之间通信的环形缓冲区以及保护该缓冲区的信号量均为全局变量。
除了使用 `QSemaphore ` 来解决生产者-消费者问题外,还可以使用 `QWaitCondition ` 和 `QMutex`。这就是“使用等待条件的生产者和消费者”示例所做的事情。
全局变量
首先,让我们回顾一下环形缓冲区及其相关的信号量:
constexpr int DataSize = 100000;
constexpr int BufferSize = 8192;
char buffer[BufferSize];
QSemaphore freeBytes(BufferSize);
QSemaphore usedBytes;DataSize 是生产者将生成的数据量。为了使示例尽可能简单,我们将它设为常量。BufferSize 是环形缓冲区的大小。它小于DataSize ,这意味着生产者最终会到达缓冲区的末尾,并从头开始重新写入。
为了同步生产者和消费者,我们需要两个信号量。freeBytes 信号量控制缓冲区的“空闲”区域(即生产者尚未填入数据或消费者已经读取过的区域)。usedBytes 信号量控制缓冲区的“已用”区域(即生产者已写入数据但消费者尚未读取的区域)。
这两个信号量共同确保生产者永远不会比消费者超前超过BufferSize 字节,并且消费者永远不会读取生产者尚未生成的数据。
freeBytes 信号量初始化为BufferSize ,因为初始时整个缓冲区是空的。usedBytes 信号量初始化为 0(如果未指定,则为默认值)。
生产者类
让我们回顾一下Producer 类的代码:
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 信号量释放一个字节。“空闲”字节已成功转换为“已用”字节,可供消费者读取。
消费者类
现在让我们来看Consumer 类:
class Consumer : public QThread
{
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() 函数
在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 信号量被释放(其初始available() 计数为 0)。 一旦生产者将一个字节写入缓冲区,freeBytes.available() 的值即为BufferSize - 1,而usedBytes.available() 的值为1。此时可能发生两种情况:要么消费者线程接管并读取该字节,要么生产者线程继续写入第二个字节。
本示例中介绍的生产者-消费者模型使得编写高度并发的多线程应用程序成为可能。在多处理器机器上,该程序的速度理论上可达等效互斥锁(mutex)程序的两倍,因为两个线程可以同时在缓冲区的不同部分进行操作。
但请注意,这些优势并非总能实现。获取和释放QSemaphore 会产生开销。实际上,将缓冲区划分为多个块并按块而非单个字节进行操作,通常更为划算。缓冲区大小也是一个必须通过实验谨慎选择的参数。
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.