当前位置: 首页 > news >正文

【QT5 多线程示例】互斥锁

互斥锁

互斥锁介绍:【C++并发编程】(三)互斥锁:std::mutex。原理都一样,这里就不赘述了。

QMutex 是 Qt 框架中提供的一个互斥锁类,主要包括以下成员函数:

  • lock():试图锁定互斥量。如果另一个线程已经锁定了这个互斥量,调用线程将被阻塞,直到那个线程解锁。
  • unlock():解锁互斥量,允许其他线程锁定它。
  • tryLock():尝试锁定互斥量,不阻塞调用线程。如果互斥量被锁定,函数返回 false;如果成功锁定,返回 true
  • tryLock(int timeout):尝试在指定的毫秒数内锁定互斥量。如果超时仍未锁定,返回 false;如果成功锁定,返回 true
  • locked():查询互斥量当前是否被锁定。如果被锁定,返回 true;否则返回 false

在简单的函数中,可以直接使用 QMutex 的 lock()unlock() 成员函数。但在复杂的函数中,使用 QMutexLocker 自动管理互斥锁更为安全和方便。QMutexLocker 与C++标准中std::lock_guard的用法差不多,而且也是基于 RAII(Resource Acquisition Is Initialization)机制的,在构造时自动锁定互斥量,在析构时自动解锁。

下面给出示例代码:
https://github.com/BinaryAI-1024/QtStudy/tree/master/thread/mutex

//myworker.h
#ifndef MYWORKER_H
#define MYWORKER_H

#include <QObject>
#include <QMutex>
#include <QDebug>

class MyWorker : public QObject
{
    Q_OBJECT
signals:
    void finished();

public:
    explicit MyWorker(QObject *parent = nullptr);

    // 获取 counter 的值
    static int getCounter();

public slots:
    void doWork(int id);

private:
    static QMutex mutex;
    static int counter;
};

#endif // MYWORKER_H

//myworker.cpp
#include "myworker.h"

// 静态成员初始化
QMutex MyWorker::mutex;
int MyWorker::counter = 0;

MyWorker::MyWorker(QObject *parent)
    : QObject(parent)
{
}

void MyWorker::doWork(int id)
{
    qDebug() << "Worker" << id << "started work.";

    for (int i = 0; i < 100000; ++i) {
        QMutexLocker locker(&mutex); // // 加锁以保护数据
        ++counter;
    }


    emit finished(); // 发送完成信号
}

int MyWorker::getCounter()
{
    return counter;
}

//main.cpp
#include <QCoreApplication>
#include <QThread>
#include "myworker.h"
#include <QTimer>
#include <QMutex>
#include <QMutexLocker>

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    QMutex mutex;                  // 创建一个互斥锁
    const int numThreads = 3;      // 定义要启动的线程数量
    QThread *threads[numThreads];  // 创建一个 QThread 指针数组,用于存储线程对象
    MyWorker *workers[numThreads]; // 创建一个 MyWorker 指针数组,用于存储工作对象
    int finishedCount = 0;         // 初始化一个计数器,用于跟踪完成的线程数量

    // 循环创建和启动线程
    for (int i = 0; i < numThreads; ++i) {
        threads[i] = new QThread; // 创建一个新线程
        workers[i] = new MyWorker; // 创建一个新工作对象

        workers[i]->moveToThread(threads[i]); // 将工作对象移动到新线程中

        // 连接线程的 started 信号到工作对象的 doWork 槽,使用 QTimer 确保在事件循环开始后执行
        QObject::connect(threads[i], &QThread::started, workers[i], [=]() {
            QTimer::singleShot(0, workers[i], [=]() { workers[i]->doWork(i); });
        });

        // 连接工作对象的 finished 信号到线程的 quit 槽,以便任务完成后退出线程的事件循环
        QObject::connect(workers[i], &MyWorker::finished, threads[i], &QThread::quit);

        // 连接工作对象的 finished 信号到工作对象的 deleteLater 槽,以便任务完成后删除工作对象
        QObject::connect(workers[i], &MyWorker::finished, workers[i], &QObject::deleteLater);

        // 连接线程的 finished 信号到线程的 deleteLater 槽,以便线程退出后删除线程对象
        QObject::connect(threads[i], &QThread::finished, threads[i], &QObject::deleteLater);

        // 连接工作对象的 finished 信号到一个 lambda 表达式,用于更新计数器并检查所有线程是否完成
        QObject::connect(workers[i], &MyWorker::finished, [&finishedCount, &mutex, i]() {
            QMutexLocker locker(&mutex); // 加锁以保护计数器的访问
            ++finishedCount; // 增加已完成线程的计数
            qDebug() << "Worker" << i << "finished. " ;
            if (finishedCount == numThreads) { // 检查是否所有线程都已完成
             	// 每个线程使counter增加100000,正确结果应该是:numThreads*100000
                qDebug() << "counter:" << MyWorker::getCounter();
            }
        });

        threads[i]->start(); // 启动线程
    }


    return app.exec();
}

结果:

Worker 1 started work.
Worker 0 started work.
Worker 2 started work.
Worker 1 finished. 
Worker 2 finished. 
Worker 0 finished. 
counter: 300000

互斥锁在代码中保护了main.cpp中的 ++finishedCount;以及mythread.cpp中的++counter;,避免了多个线程同时执行这些操作导致的错误。QMutexLocker能够自动管理局部作用域内互斥锁的加锁和开锁。

另外,在这段代码中,不需要调用 thread.wait() 是因为 Qt 的信号和槽机制自动管理了线程的生命周期。通过 QThread::quit()QObject::deleteLater(),线程在任务完成后自动退出并清理资源。

相关文章:

  • QWen 和 DeepSeek 入门指南
  • 天梯赛 L2-012 关于堆的判断
  • 光谱仪与光谱相机的核心区别与协同应用
  • 使用 AnythingLLM 轻松部署本地知识库!
  • 雷池SafeLine-自定义URL规则拦截非法请求
  • 【MySQL】触发器与存储引擎
  • 基于开源模型的微调训练及瘦身打造随身扫描仪方案__用AI把手机变成文字识别小能手
  • 第二章 EXI协议原理与实现--7.5 Efficient XML库和OpenEXI.jar编解码交叉测试
  • Linux网络相关概念和重要知识(2)(UDP套接字编程、聊天室的实现、观察者模式)
  • XGBoost
  • 1987-2023年各省进出口总额数据整理(含进口和出口)(无缺失)
  • 目标检测中归一化的目的?
  • 大模型agent的构建
  • Python高级——类的知识
  • 域渗透工具推荐:impacket
  • 第6关:部分快速排序-用分治法实现
  • 2.Linux基本指令(下)
  • conda报错activate没办法激活环境
  • 从TouchDriver Pro到Touchdriver G1,Weart触觉手套全系解析:XR交互的“真实触感”如何实现?
  • 【北京迅为】iTOP-RK3568开发板OpenHarmony系统南向驱动开发UART接口运作机制
  • 政企共同发力:多地密集部署外贸企业抢抓90天政策窗口期
  • 中日东三省问题的源起——《1905年东三省事宜谈判笔记》解题
  • 一个多月来上海交大接连“牵手”三区,在这些方面进行区校合作
  • 王征、解宁元、牛恺任西安市副市长
  • KPL“王朝”诞生背后:AG和联赛一起迈向成熟
  • 郑培凯:汤显祖的“至情”与罗汝芳的“赤子之心”