您的位置:首页 > 大数据 > 人工智能

Wait Condition 例子

2015-08-28 20:41 323 查看
翻译自Qt帮助文档:Wait Condition Example

用Qt证明多线程编程。

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

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

Wait Conditions 使高级并发成为可能,比单独使用mutexes的可能性大。如果想访问QMutex控制的缓冲区,消费者线程和生产者线程不能同时访问缓冲区。然而,让两个线程同时访问缓冲区的不同部分是没有什么害处的。

这个例子包括两个类:生产者(Producer)和 消费者(Consumer)。它们都继承了QThread。这两个类通过循环缓冲区交互,保护缓冲区的同步工具设为全局变量。

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

全局变量

让我们从循环缓冲区和相关的同步工具开始吧:

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

QWaitCondition bufferNotEmpty;
QWaitCondition bufferNotFull;
QMutex mutex;
int numUsedBytes = 0;


DataSize 是生产者将要产生的数据量大小。为了尽量使例子简单些,将它设为常量。

BufferSize是循环缓冲区的大小。它比DataSize小,意味着某一时候生产者将会到达缓冲区的尾部,然后重新回到缓冲区起始端。

为了同步生产者和消费者,我们需要两个 wait condition和一个mutex。当生产者已经产生一些数据时 发送bufferNotEmpty condition 信号,告诉消费者它可以开始读数据了。当消费者已经读到一些数据时 发送 bufferNotFull condition信号,告诉生产者它可以产生更多的数据了。numUsedBytes是缓冲区中包含了数据的字节数。

同时,wait conditions,mutex,以及numUsedBytes计数器 确保了生产者永远不会超过消费者 BufferSize 字节,并且消费者永远不会去读生产者还未产生出来的数据。

生产者类

让我们从生产者的类开始吧:

class Producer:public QThread
{
public:
Producer(QObject *parent=NULL):QThread(parent)
{
}
void run()
{
qsrand(QTimer(0,0,0).secsTo(QTime::currentTime()));

for(int i=0; i<DataSize; ++i){
mutex.lock();
if(numUsedBytes == BufferSize)
{
<span style="background-color: rgb(255, 255, 153);">bufferNotFull.wait(&mutex);</span>
}
mutex.unlock();

buffer[i%BufferSize] = "ACGT"[(int)qrand()%4];

mutex.lock();
++numUsedBytes;
<span style="background-color: rgb(255, 204, 255);">bufferNotEmpty.wakeAll();</span>
mutex.unlock();
}
}
};
生产者产生DataSize字节数据。在它往循环缓冲区些一个字节前,它必须先检查缓冲区是否满了(即 numUsedBytes=BufferSize)。如果缓冲区已满,该线程等待 bufferNotFull condition。

最后,生产者使用mutex 增加 numUsedBytes 变量值,如果 numUsedBytes > 0 就通知condition bufferNotEmpty 是true。

我们通过mutex控制所有对numUsedBytes 变量的访问。另外,QWaitCondition::wait() 函数将mutex作为实参。在线程进入睡眠状态前解锁mutex,并当线程被唤醒时锁住mutex。而且,从锁住状态转换到等待状态是原子的(即支持多线程并发),是为了竞争条件的产生。

消费者类

让我们转到消费者类:

class Consumer:public QThread
{
Q_OBJECT
public:
Consumer(QObject *parent=NULL):QThread(parent)
{
}
void run()
{
for(int i=0; i<DataSize; ++i){
mutex.lock();
if(numUsedBytes == 0)
{
<span style="background-color: rgb(255, 204, 255);">bufferNotEmpty.wait(&mutex)</span>;
}
mutex.unlock();

fprintf(sederr,"%c",buffer[i%BufferSize]);

mutex.lock();
--numUsedBytes;
<span style="background-color: rgb(255, 255, 153);"> bufferNotFull.wakeAll();</span>
mutex.unlock();
}
fprintf(stderr,"\n");
}

signals:
void stringConsumed(const QString &text);
};
这个代码与生产者类似。在读字节之前,我们检查缓冲区是否是空的(numUsedBytes =0),而不是检查它是不是满的,如果它时空的就等待 bufferNotEmpty condition。在我们读到字节之后,我们减少 numUsedBytes(而不是增加),然后给 bufferNotFull condition 发信号(而不是 bufferNotEmpty condition)。

主函数

在主函数中,我们创建两个线程,并调用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;
}
那么当我们运行程序时会发生什么呢?刚开始的时候,生产者线程时唯一的一个可以做事情的线程,消费者被阻塞等待bufferNotEmpty condition(numUsedBytes=0)触发。一旦生产者往缓冲区中写入一个字节,numUsedBytes = BufferSize-1,并且bufferNotEmpty condition被触发。同时,有可能发生两件事:要么消费者线程接着执行,要么生产者接着产生第一个字节。

这个例子展示的生产者-消费者模式使那些写出高并发的多线程应用程序称为可能。在一个多处理器机器上,这个程序的效率可能是那些基于mutex的程序两倍,因为处理缓冲区的不同部分时可以运行两个线程。
注意,尽管经常未意识到这些好处,获取和释放一个 QMutex 需要付出一个代价。事实上,这样做是值得的:将缓冲区分成数块(chunks),然后操作块而不是单个字节。基于实验,选取缓冲大小时要留心。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: