Qt多线程——QWaitCondition

1. 什么是 QWaitCondition

QWaitCondition也是用于多线程同步的。线程 A 调用 QWaitCondition::wait()函数后,处于等待阻塞状态;线程 B 调用 QWaitCondition::wake()函数后 唤醒 线程 A,线程 A 就会继续执行。

下面再通过一个例子理解一下:线程 A 通过调用 send()函数,发送一个数据包给 线程 B ,此时线程 A 处于阻塞等待状态,直到线程 B 接收到这个数据包并将其处理完毕之后,再将其传回线程 A 并且 再唤醒线程 A 。

2. 示例

// 线程 A
send(&packet);
mutex.lock();
condition.wait(&mutex); 
if (m_receivedPacket)
{
    handle_packet(m_receivedPacket); // 线程B传回来的包
}
mutex.unlock();


// 线程 B
m_receivedPacket = parse_packet(buffer);  // 将接收的数据解析成包
condition.wakeAll();

以上的示例代码是存在一个问题的。

如果 线程 A 调用 send()函数后,线程 B 立马将解析完的数据包返回 并且 唤醒线程 A,此时线程 A 还没来得及调用 condition.wait()函数。那么很明显,线程 B 的唤醒是无效的,等到 线程 A 真正调用condition.wait()函数的时候,就会一直阻塞在这里。

3. 解决方法

首先我们要保证这样的一个顺序:线程 A 发送数据包给线程 B —> 线程 A 进入等待阻塞状态 —> 线程 B 将解析完的数据包发回线程 A —> 线程 B 唤醒 线程 A —> 线程 A 再完成剩下的操作。

我们参考 Qt官方示例:

  forever {
      mutex.lock();
      keyPressed.wait(&mutex);
      do_something();
      mutex.unlock();
  }

  forever {
      getchar();
      keyPressed.wakeAll();
  }

将之前的示例稍作修改:

// 修改后的示例

// 线程 A
mutex.lock();
send(&packet);
condition.wait(&mutex); 
if (m_receivedPacket)
{
    handle_Ppacket(m_receivedPacket); // 线程B传回来的包
}
mutex.unlock();


// 线程 B
m_receivedPacket = parse_packet(buffer);  // 将接收的数据解析成包
mutex.lock();
condition.wakeAll();
mutex.unlock();

修改后的上述示例代码,线程 A 先获取 mutex锁,即从发送数据包开始,一直到 QWaitCondition::wait()在操作系统层次真正执行阻塞等待指令,这一段线程 A 的时间段内,mutex一直被上锁,即使 线程 B 很快就接收到数据包,也不会直接调用 wakeAll(),而是在调用 mutex.lock()时阻塞住(因为 线程 A 已经获取mutex锁了,再尝试上锁就会被阻塞),直到 线程 A QWaitCondition::wait()真正执行操作系统的阻塞等待指令并释放mutex,线程 B 的 mutex.lock()才即出阻塞,继续往下执行,调用 wakeAll(),此时一定能唤醒 线程 A 成功。

这样我们就保证了 先调用 wait(),再调用 wake()

大致的流程:

  • 线程 A mutex.lock()获得锁,上锁;
  • 线程 A wait()成功后释放锁,进入阻塞状态(OS保证wait_block阻塞的瞬间释放锁);
  • 线程 B mutex.lock()获得锁,上锁;
  • 线程 B 调用 condition.wakeAll()OS的进程调度器尝试唤醒关联的所有线程,所以主线程进入唤醒状态,并尝试获得mutex锁。此时mutex已被 线程B 获得,所以 线程 A 肯定无法获得mutex锁,进程调度器决定让主线程继续阻塞。注意,区别于之前的wait_block调用阻塞,这次阻塞是上锁阻塞);
  • 线程 A mutex.unlock()释放锁,进程调度器尝试唤醒这个锁关联的某一个线程;
  • 线程 A 作为 mutex唯一关联的线程,获得了锁mutex.lock()wait()终于返回;
  • 线程 A 处理完的业务逻辑,调用mutex.unlock()释放锁。

4. 用 QWaitCondition实现 生产者 / 消费者模型

(1) 头文件、和一些变量

#include <QCoreApplication>

#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <stdio.h>

const int dataSize = 1000;
const int bufferSize = 100;

int buffer[bufferSize];

QWaitCondition buffer_empty;
QWaitCondition buffer_full;

//用互斥量 保证线程的原子操作
QMutex mutex;

//缓冲区可用空间
int available_space = 0;

//index 指向要读取的缓冲区位置
int index = 0;

(2) 生产者线程

//生产者
class Producer : public QThread{
public:
    Producer();
    void run() override;
};

Producer::Producer(){}

void Producer::run(){
    for(int i = 0;i < dataSize;i++){
        mutex.lock();

        //缓冲区是否已经 被 生产者生产的数据填满
        //如果已经填满,则等待 “缓冲区有空闲空间” 条件 (buffer_empty) 成立
        if(available_space == bufferSize) buffer_empty.wait(&mutex);

        //若缓冲区未被填满,则向其写入一个整数值
        buffer[i % bufferSize] = available_space;

        //填充空间数+1
        available_space++;

        //唤醒 等待“缓冲区数据已经填满” (buffer_full 条件) 为 “真” 的线程
        buffer_full.wakeAll();

        mutex.unlock();
    }
}

(3) 消费者线程

//消费者
class Consumer : public QThread{
public:
    Consumer();
    void run() override;
};

Consumer::Consumer(){}

void Consumer::run(){
    forever{
        mutex.lock();

        if(available_space == 0) buffer_full.wait(&mutex);

        printf("%ul::[%d] = %d\n",currentThreadId(),index,buffer[index]);
        index = (index + 1) % bufferSize;
        --available_space;

        //唤醒等待 “缓冲区有空位” (buffer_empty 变量)条件的生产者线程
        buffer_empty.wakeAll();
        mutex.unlock();
    }
    printf("\n");
}

(4) 启动一个生产者线程 和 两个消费者线程

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Producer producer;
    Consumer c1 , c2;

    producer.start();
    c1.start();
    c2.start();

    producer.wait();
    c1.wait();
    c2.wait();

    return a.exec();
}

1. 完整代码

#include <QCoreApplication>

#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <stdio.h>

const int dataSize = 1000;
const int bufferSize = 100;

int buffer[bufferSize];

QWaitCondition buffer_empty;
QWaitCondition buffer_full;

//用互斥量 保证线程的原子操作
QMutex mutex;

//缓冲区可用空间
int available_space = 0;

//index 指向要读取的缓冲区位置
int index = 0;

//生产者
class Producer : public QThread{
public:
    Producer();
    void run() override;
};

Producer::Producer(){}

void Producer::run(){
    for(int i = 0;i < dataSize;i++){
        mutex.lock();

        //缓冲区是否已经 被 生产者生产的数据填满
        //如果已经填满,则等待 “缓冲区有空闲空间” 条件 (buffer_empty) 成立
        if(available_space == bufferSize) buffer_empty.wait(&mutex);

        //若缓冲区未被填满,则向其写入一个整数值
        buffer[i % bufferSize] = available_space;

        //填充空间数+1
        available_space++;

        //唤醒 等待“缓冲区数据已经填满” (buffer_full 条件) 为 “真” 的线程
        buffer_full.wakeAll();

        mutex.unlock();
    }
}


//消费者
class Consumer : public QThread{
public:
    Consumer();
    void run() override;
};

Consumer::Consumer(){}

void Consumer::run(){
    forever{
        mutex.lock();

        if(available_space == 0) buffer_full.wait(&mutex);

        printf("%ul::[%d] = %d\n",currentThreadId(),index,buffer[index]);
        index = (index + 1) % bufferSize;
        --available_space;

        //唤醒等待 “缓冲区有空位” (buffer_empty 变量)条件的生产者线程
        buffer_empty.wakeAll();
        mutex.unlock();
    }
    printf("\n");
}


int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Producer producer;
    Consumer c1 , c2;

    producer.start();
    c1.start();
    c2.start();

    producer.wait();
    c1.wait();
    c2.wait();

    return a.exec();
}

2. 运行结果

在这里插入图片描述

参考博客

QWaitCondition 的正确使用方法

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Qt是一个跨平台的应用程序框架,提供了丰富的多线程编程支持。Qt多线程编程主要依靠QThread类和信号槽机制来实现。 QThread类封装了线程的基本操作,使得我们可以通过继承这个类来实现自己的线程。通过重写QThread类中的run()方法,我们可以在这个方法中实现具体的线程操作。 例如,下面是一个简单的QThread子类的定义: ```c++ class MyThread : public QThread { Q_OBJECT public: explicit MyThread(QObject *parent = nullptr); protected: void run() override; signals: void resultReady(int result); }; ``` 在这个例子中,我们重写了run()方法来实现线程的具体操作。在这个方法中,我们可以调用其他Qt类或者自己实现的函数来完成多线程的任务。另外,我们还定义了一个resultReady信号,用于在线程执行完毕后向主线程发送消息。 接下来,我们可以在主线程中创建一个MyThread对象,并连接它的resultReady信号到一个槽函数中,以便在线程执行完毕后处理结果。例如: ```c++ MyThread *thread = new MyThread(this); connect(thread, &MyThread::resultReady, this, &MyClass::handleResult); thread->start(); ``` 在这个例子中,我们创建一个MyThread对象并启动它。在线程执行完毕后,它会发送resultReady信号,我们将这个信号连接到handleResult槽函数中来处理结果。 除了QThread类外,Qt还提供了许多其他的多线程编程工具,如QThreadPool类、QMutex类、QWaitCondition类等,可以帮助我们更方便地实现多线程编程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值