• 70.Qt QWaitCondition多线程同步介绍及案例分析


    1.QWaitCondition介绍

    QWaitCondition 用于多线程的同步,一个线程调用QWaitCondition::wait() 阻塞等待,直到另一个线程调用QWaitCondition::wake() 唤醒才继续往下执行

    QWaitCondition常用函数如下所示:

    1. bool QWaitCondition::wait(QMutex *lockedMutex, QDeadlineTimer deadline = QDeadlineTimer(QDeadlineTimer::Forever));
    2. //释放lockedMutex并等待一个等待条件。
    3. //如果lockedMutex未处于锁定状态,则行为为undefined。直到满足以下任一条件:
    4. //1.另一个线程使用wakeOne () 或wakeAll () 发出信号。在这种情况下,此函数将返回 true。
    5. //2.已达到deadline参数期限给定的最后期限,默认为QDeadlineTimer::Forever,那么等待将永远不会超时(必须发出信号wakeOne或者wakeAll),如果等待超时,此函数将返回 false。
    6. void QWaitCondition::wakeAll()
    7. //唤醒所有调用wait()进行等待的线程。线程被唤醒的顺序取决于操作系统的调度策略,无法控制或预测。
    8. void QWaitCondition::wakeOne()
    9. //随机唤醒一个调用wait()进行等待的线程。被唤醒的线程取决于操作系统的调度策略,无法控制或预测。

    2.官方示例

    假如我们有3个子线程,run函数下执行:

    1. while(1) {
    2. mutex.lock(); // mutex是一个全局共享互斥量
    3. btnPressed.wait(&mutex); // btnPressed 为 QWaitCondition
    4. do_something();
    5. mutex.unlock();
    6. }

    主界面有一个按键,当按下的时候就会激活3个子线程做相关事情操作,代码如下所示:

    1. onClicked() {
    2. btnPressed.wakeAll(); // 唤醒所有线程
    3. }

     示例如下所示,头文件:

    1. extern QMutex mutex;
    2. extern QWaitCondition btnPressed;
    3. class Thread : public QThread
    4. {
    5. Q_OBJECT
    6. protected:
    7. void run() override{
    8. while(1) {
    9. mutex.lock();
    10. qDebug()<<"进入等待"<<_delayMs;
    11. btnPressed.wait(&mutex);
    12. qDebug()<<"已唤醒 进入延时"<<_delayMs;
    13. msleep(_delayMs);
    14. mutex.unlock();
    15. }
    16. }
    17. public:
    18. Thread(int delayMs, QObject *parent = nullptr) : QThread(parent), _delayMs(delayMs)
    19. { };
    20. private:
    21. int _delayMs;
    22. };
    23. class Widget : public QWidget
    24. {
    25. Q_OBJECT
    26. public:
    27. Widget(QWidget *parent = nullptr);
    28. ~Widget();
    29. private slots:
    30. void on_pushButton_clicked();
    31. private:
    32. Ui::Widget *ui;
    33. Thread* t1;
    34. Thread* t2;
    35. Thread* t3;
    36. };

    源文件:

    1. QMutex mutex;
    2. QWaitCondition btnPressed;
    3. Widget::Widget(QWidget *parent)
    4. : QWidget(parent)
    5. , ui(new Ui::Widget)
    6. {
    7. ui->setupUi(this);
    8. t1 = new Thread(100);
    9. t2 = new Thread(2000);
    10. t3 = new Thread(3000);
    11. t1->start(QThread::NormalPriority);
    12. t2->start(QThread::NormalPriority);
    13. t3->start(QThread::NormalPriority);
    14. }
    15. Widget::~Widget()
    16. {
    17. delete ui;
    18. t1->quit();
    19. t2->quit();
    20. t3->quit();
    21. }
    22. void Widget::on_pushButton_clicked()
    23. {
    24. btnPressed.wakeAll();
    25. }
    • 当我们按下按键后,三个线程就会随机按顺序进行激活执行.执行的总时长会是5100ms(3个子线程的总时长).

    假如我们此时线程1正在do_something()时,主界面再次按下了按键,由于等待的三个线程有一个未完成,那么该三个线程将不会再次执行.
    如果要要优化,可以使用计数器和QMutex来保护它来解决这个问题。

    2.案例分析2-数据库线程

    主界面需要将多个很长数据任务存到数据库中,为了避免阻塞主界面,假如,我们有一个数据库线程,一个主界面线程:

    • 主界面线程:  通过QQueue队列往数据库线程发送task任务
    • 数据库线程(DbWorker): 读出QQueue队列的任务,然后进行存储到数据库中

    假如没有QWaitCondition,我们的数据库线程一般有两种方式:

    • 方法1-必须一直running,定时判断QQueue队列是否有数据,缺点在于: 假如定时间隔大会影响效率.定时间隔小浪费资源
    • 方法2-每次start需要打开数据库,执行一次队列所有数据出队后,关闭数据库,等待下次start再打开数据库, 缺点在于: 频繁开关数据库,浪费时间

    有QWaitCondition后

    • start线程后,需要打开数据库,线程执行一次队列所有数据出队后,通过QWaitCondition::wait等待10S,如果主界面线程调用QWaitCondition::wakeAll()唤醒后.则线程再次反复执行一次队列所有数据出队后,再次通过QWaitCondition::wait等待10S,直到未唤醒,最后进行一次数据库关闭.

    示例如下所示

    主界面调用DbWorker线程的公共函数:

    1. bool DbWorker::enqueueTask(DataTask* task)
    2. {
    3. mutex.lock();
    4. taskQueue.enqueue(task); // 入队
    5. mutex.unlock();
    6. if(this->isRunning()) {
    7. waitc.wakeAll(); // 唤醒线程 waitc 为 QWaitCondition
    8. } else {
    9. this->start(QThread::HighPriority);
    10. }
    11. return true;
    12. }

    数据库线程(DbWorker):

    1. void DbWorker::run()
    2. {
    3. QSqlDatabase* db = dbInit(); // 打开数据库
    4. while(true) {
    5. DataTask* task;
    6. if(taskQueue.count()) {
    7. mutex.lock();
    8. task = taskQueue.dequeue(); // 出队
    9. mutex.unlock();
    10. taskHandler(); // 进行任务处理,存到数据中
    11. task->deleteLater();
    12. } else {
    13. waitmutex.lock();
    14. unsigned long timeout = 10000;
    15. bool ret = waitc.wait(&_waitmutex, timeout); // 最多等待 10s,假如ret为true,则说明队列有新数据要处理了,如果为false则到时间了
    16. do_something();
    17. waitmutex.unlock();
    18. mutex.lock();
    19. if(!taskQueue.count()) {
    20. mutex.unlock();
    21. break; // 等待10后,队列还是没有数据,则退出了
    22. }
    23. mutex.unlock();
    24. }
    25. }
    26. if(db) { // 最后关闭数据
    27. delete db;
    28. db = nullptr;
    29. }
    30. }


    未完待续,后面有其它案例再来追加.

  • 相关阅读:
    广东MES系统实现设备管理的方法与功能
    TCP协议:超时重传、流量控制、keep-alive和端口号,你真的了解吗?
    软考-防火墙技术与原理
    电力系统强大的Gurobi 求解器的学习(Python&Matlab)
    数组进阶提高
    Leetcode 496.下一个更大元素Ⅰ
    适用于全部安卓手机的 5 大免费 Android 数据恢复
    英语语法汇总(13.虚拟语气)
    Android11去掉Setings里的投射菜单条目
    全程免费的ssl证书申请——七步实现网站https
  • 原文地址:https://blog.csdn.net/qq_37997682/article/details/126313012