信号量(Semaphore) 例子

翻译自Qt帮助文档:Semaphore Example


使用Qt证明多线程编程。

这个信号量例子展示了如何使用 QSemaphore 来控制对一个循环缓冲区的访问,生产者线程和消费者线程共享该缓冲区。

生产者线程往缓冲区写数据,直到缓冲区的尾部,然后从缓冲区开头重新开始重写已经存在的数据。当数据被生产出来后消费者线程读取数据并写道标准错误中。

与互斥量(mutex)相比,信号量(semaphore)使高效并行成为可能。如果使用QMutex控制访问缓冲区,消费者线程不可能和生产者线程同时访问缓冲区。然而,两个线程同时访问缓冲区的不同部分并没什么害处。

这个例子包括两个类: Producer 和 Consumer 。这两个类都继承了QThread。这两个线程通过循环缓冲区交互,信号量被设为全局的,它保护这种交互。

使用QSemaphore 可以解决“生产者-消费者”的问题,另一种替代方法是使用 QWaitCondition 和 QMutex。


全局变量

让我们从循环缓冲区和相关的信号量开始吧。

const int DataSize = 100000;

const int BufferSize = 8192;
char buffer[BufferSize];

QSemaphore freeBytes(BufferSize);
QSemaphore usedBytes;
DataSize 是生产者将要产生的数据的数量。为了使这个例子尽量简单,我们将它设为常量。

BufferSize 是循环缓冲区的大小。它小于DataSize,就意味着在某种程度上,生产者将会到达缓冲区的结尾处并重新从头开始。

为了同步生产者和消费者,我们需要两个信号量:

freeBytes 信号量控制着缓冲区的“free”区域(即生产者未写数据的区域,或者消费者已经读过的区域)。

usedBytes 信号量控制着缓冲区的“used”区域(即生产者已经写了数据的但消费者还未读取的区域)。

同时,信号量确保生产者永远不会超过消费者 BufferSize 字节,并且消费者永远不会读到生产者还未产生的数据。

freeBytes 信号量用BufferSize初始化,因为一开始整个缓冲区是空的。

usedBytes 信号量初始化为0(默认值,如果未指定任何值)。


生产者类

让我们看下生产者类的代码:

class Producer:public QThread
{
public:
    void run()
    {
         qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
         for(int  i=0; ifreeBytes.acquire();
            buffer[i%BufferSize] = "ACGT"[(int)qrand()%4];//qrand()%4 : 0,1,2,3;
            usedBytes.release();
        }
    }
};
生产者产生了DataSize字节的数据。在写到循环缓冲区之前,必须使用 freeBytes 信号量获得一个“free”字节。如果消费者没有跟上生产者的速度,QSemaphore::acquire() 调用可能会阻塞。

最后,生产者使用 usedBytes 信号量释放一个字节。“free”的字节被成功的转移到一个”used“字节中,以备消费者读取。


消费者类

现在我们转到 Consumer 类:

class Consumer : public QThread
{
    Q_OBJECT
public:
    void run()
    {
        for(int i=0; i
此处代码与生产者的类似,除了这次我们获得一个“used”字节,释放一个“free”字节,而不是相反的。


主函数

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() 的 count 值是 0)。一旦生产者往缓冲区写一个字节,freeBytes.available() 就是 BufferSize -1,并且 usedBytes.available() = 1。此时,将会发生两件事:要么消费者线程接收并读取那个字节,要么生产者继续产生第一个字节。

这个例子展示的生产者-消费者模式使那些写出高并发的多线程应用程序称为可能。在一个多处理器机器上,这个程序的效率可能是那些基于mutex的程序两倍,因为处理缓冲区的不同部分时可以运行两个线程。

注意,尽管经常未意识到这些好处,获取和释放一个 QSemaphore 需要付出一个代价。事实上,这样做是值得的:将缓冲区分成数块(chunks),然后操作块而不是单个字节。基于实验,选取缓冲大小时要留心。






你可能感兴趣的:(Qt)