使用 Semaphores 的生产者和消费者
使用 Semaphores 的生产者和消费者示例展示了如何使用QSemaphore 来控制对生产者线程和消费者线程共享的循环缓冲区的访问。
生产者向缓冲区写入数据,直到写到缓冲区的末端,然后从头开始重新写入,覆盖现有数据。消费者线程读取生成的数据,并将其写入标准错误。
与互斥相比, Semaphores 可以实现更高水平的并发。如果对缓冲区的访问受QMutex 的保护,消费者线程就无法与生产者线程同时访问缓冲区。不过,让两个线程同时处理缓冲区的不同部分也没什么坏处。
该示例包括两个类:Producer
和Consumer
。两个类都继承自 。这两个类都继承自QThread 。这两个类之间用于通信的循环缓冲区和保护缓冲区的 Semaphores 都是全局变量。
除了使用QSemaphore 来解决生产者-消费者问题外,另一种方法是使用QWaitCondition 和QMutex 。这就是使用等待条件的生产者和消费者示例所做的事情。
全局变量
让我们先回顾一下循环缓冲区和相关的 Semaphores:
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
semaphore 初始化为 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
semaphore 获取一个 "空闲 "字节。如果消费者没有跟上生产者的节奏,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
semaphore 被释放(其初始available() 计数为 0)。生产者在缓冲区中放入一个字节后,freeBytes.available()
为BufferSize
- 1,usedBytes.available()
为 1。此时,会发生两种情况:要么消费者线程接管并读取该字节,要么生产者线程生产第二个字节。
本例中介绍的生产者-消费者模型使编写高度并发的多线程应用程序成为可能。在多处理器机器上,由于两个线程可以同时作用于缓冲区的不同部分,因此程序的速度可能是基于互斥的程序的两倍。
不过要注意的是,这些优势并不总是能实现。获取和释放QSemaphore 是有代价的。在实际操作中,将缓冲区划分为若干小块,并对小块而不是单个字节进行操作可能会更有价值。缓冲区大小也是一个参数,必须根据实验结果谨慎选择。
© 2025 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.