前言
此记录为刷leetcode 过程中复习Qt多线程的意外收获,也是对自己使用Qt过程中疑问的回答
一、官方自带生产者消费者源码分析
1、官方Wait Conditions Example源码
#include <QtCore>
#include <stdio.h>
#include <stdlib.h>
//! [0]
const int DataSize = 100000;
const int BufferSize = 8192;
char buffer[BufferSize];
QWaitCondition bufferNotEmpty;
QWaitCondition bufferNotFull;
QMutex mutex;
int numUsedBytes = 0;
//! [0]
//! [1]
class Producer : public QThread
//! [1] //! [2]
{
public:
Producer(QObject *parent = NULL) : QThread(parent)
{
}
void run() override
{
for (int i = 0; i < DataSize; ++i) {
mutex.lock();
if (numUsedBytes == BufferSize)
bufferNotFull.wait(&mutex);
mutex.unlock();
buffer[i % BufferSize] = "ACGT"[QRandomGenerator::global()->bounded(4)];
mutex.lock();
++numUsedBytes;
bufferNotEmpty.wakeAll();
mutex.unlock();
}
}
};
//! [2]
//! [3]
class Consumer : public QThread
//! [3] //! [4]
{
Q_OBJECT
public:
Consumer(QObject *parent = NULL) : QThread(parent)
{
}
void run() override
{
for (int i = 0; i < DataSize; ++i) {
mutex.lock();
if (numUsedBytes == 0)
bufferNotEmpty.wait(&mutex);
mutex.unlock();
fprintf(stderr, "%c", buffer[i % BufferSize]);
mutex.lock();
--numUsedBytes;
bufferNotFull.wakeAll();
mutex.unlock();
}
fprintf(stderr, "\n");
}
signals:
void stringConsumed(const QString &text);
};
//! [4]
//! [5]
int main(int argc, char *argv[])
//! [5] //! [6]
{
QCoreApplication app(argc, argv);
Producer producer;
Consumer consumer;
producer.start();
consumer.start();
producer.wait();
consumer.wait();
return 0;
}
//! [6]
#include "waitconditions.moc"
这个demo的大体意思是:现在有8192个字节的一个大坑,生产者线程往里面填,消费者线程使劲的挖,谁快了就停下来等着对着,直到100000个字节丢完了为止。
二、疑问及源码解析
1、疑点:互斥量加锁不释放,难道不死锁么?
摘录部分消费者代码,疑问来了,条件变量bufferNotEmpty与互斥量到底什么关系,wait个啥子,互斥量加锁,不释放是个什么鬼,程序还如何继续往下执行呢?,代码如下:
void run() override
{
for (int i = 0; i < DataSize; ++i) {
mutex.lock();
if (numUsedBytes == 0)
bufferNotEmpty.wait(&mutex); //条件变量bufferNotEmpty在此等待,互斥量mutex不释放,如何继续往下执行?
mutex.unlock();
fprintf(stderr, "%c", buffer[i % BufferSize]);
mutex.lock();
--numUsedBytes;
bufferNotFull.wakeAll();
mutex.unlock();
}
fprintf(stderr, "\n");
}
打开Qt官网的帮助文档,得到解释如下:
bool QWaitCondition::wait(QMutex *lockedMutex, unsigned long time = ULONG_MAX)
Releases the lockedMutex and waits on the wait condition. The lockedMutex must be initially locked by the calling thread. If lockedMutex is not in a locked state, the behavior is undefined. If lockedMutex is a recursive mutex, this function returns immediately. The lockedMutex will be unlocked, and the calling thread will block until either of these conditions is met:
Another thread signals it using wakeOne() or wakeAll(). This function will return true in this case.
time milliseconds has elapsed. If time is ULONG_MAX (the default), then the wait will never timeout (the event must be signalled). This function will return false if the wait timed out.
The lockedMutex will be returned to the same locked state. This function is provided to allow the atomic transition from the locked state to the wait state.
画重点,释放锁,并在此等待条件变量;解释了上一个疑问,它把锁释放掉了,可以继续往下执行;但是既然把锁释放了,那 bufferNotEmpty.wait(&mutex;后面那接下来的mutex.unlock()又是个什么鬼,重复释放?这么大的库不至于犯这种问题吧。
2.疑点:Qt会重复unlock么?
想着源码也不会太复杂,那就看看源码了。
bool QWaitCondition::wait(QReadWriteLock *readWriteLock, unsigned long time)
{
if (!readWriteLock)
return false;
auto previousState = readWriteLock->stateForWaitCondition();
if (previousState == QReadWriteLock::Unlocked)
return false;
if (previousState == QReadWriteLock::RecursivelyLocked) {
qWarning("QWaitCondition: cannot wait on QReadWriteLocks with recursive lockForWrite()");
return false;
}
QWaitConditionEvent *wce = d->pre();
readWriteLock->unlock(); //1、释放锁
bool returnValue = d->wait(wce, time); //2、这里面等待事件发生,这才是真正的wait,windows平台下是WaitForSingleObjectEx(), 以前搞MFC的东西这会用上了,得瑟下
if (previousState == QReadWriteLock::LockedForWrite) //3、等待结束,再次上锁
readWriteLock->lockForWrite();
else
readWriteLock->lockForRead();
d->post(wce, returnValue);
return returnValue;
}
有了以上的源码,不用多解释了吧,它真的把锁释放掉了,不过换成了等待事件;从整个过程来说,先释放锁,后加锁,成对出现,对用户透明,又实现了等待过程;顺便贴上等待的部分的源码:
bool QWaitConditionPrivate::wait(QWaitConditionEvent *wce, unsigned long time)
{
// wait for the event
bool ret = false;
switch (WaitForSingleObjectEx(wce->event, time, FALSE)) { //windows平台下,或者搞过MFC的娃不用多说,直接msdn就好了
default: break;
case WAIT_OBJECT_0:
ret = true;
break;
}
return ret;
}
三、总结
QWaitCondition需要与QMutex配合使用;
源码比较简单,也很巧妙,值得学习;
有问题,先文档,再源码,最后百度(没法google);
希望自己后面还有时间扣扣源码,记录点滴,在这条路上越走越远。