QWaitCondition源码解析(暨生产者消费者线程分析)

本文深入解析Qt中生产者消费者模型的实现细节,重点分析官方WaitConditionsExample源码,探讨互斥量与条件变量的巧妙配合,解答Qt多线程开发中的常见疑惑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

此记录为刷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);
希望自己后面还有时间扣扣源码,记录点滴,在这条路上越走越远。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值