• 【Qt】Qt中多线程的使用


    Qt的多线程机制允许开发者在单个应用程序进程中创建并行任务,从而实现高效的资源利用和流畅的用户体验。

    1. QThread 类

    • Qt 提供了 QThread 类来实现跨平台的多线程功能。
    • 每个 QThread 对象代表一个操作系统级别的线程。
    • 不建议直接在 QThread 对象的子类中覆盖 run() 函数并在其中执行长耗时任务;而是应该创建一个工作类,继承自 QObject,并在线程中移动这个工作对象,使其在新线程的事件循环中执行任务。

    头文件

    #include
    
    • 1

    所属模块:

    QT += core
    
    • 1

    2. 线程的创建与启动

    • 创建一个新的线程通常包括实例化一个 QThread 对象,创建一个要在新线程上执行任务的工作类实例,并将该工作类移动到新线程中。
    • 通过调用 QThread::start() 方法启动线程,这会触发 QThread 对象的 run() 函数,但在现代Qt实践下,run() 函数主要用于启动事件循环而非执行具体业务逻辑。

    3. 线程间通信

    • Qt的信号和槽机制可用于线程间的通信,由于它是类型安全且异步的,所以特别适合于多线程环境。
    • 使用 moveToThread() 函数将一个QObject移动到另一个线程中后,其发出的信号会在目标线程的事件循环中排队,通过连接信号到槽,可以在不同线程之间安全地传递数据和控制流。
    // Worker.h
    #include 
    
    class Worker : public QObject
    {
        Q_OBJECT
    public:
        using QObject::QObject;
    
    public slots:
        void doWork() {
            // 这里放置你要在新线程中执行的具体任务
            for (int i = 0; i < 1000000; ++i) {
                // 假设这是一个耗时的操作
                // ...
                qDebug() << "Working in thread:" << QThread::currentThreadId();
            }
    
            emit workFinished();
        }
    
    signals:
        void workFinished();
    };
    
    // 主程序
    #include 
    #include 
    #include "Worker.h"
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        // 创建QThread对象
        QThread workerThread;
        // 创建Worker对象
        Worker worker;
        // 将Worker对象移动到新线程中
        worker.moveToThread(&workerThread);
    
        // 连接信号槽,当线程开始运行时,执行doWork()
        connect(&workerThread, &QThread::started, &worker, &Worker::doWork);
        // 当Worker完成任务时,退出线程
        connect(&worker, &Worker::workFinished, &workerThread, &QThread::quit);
        // 当线程结束时,删除Worker对象
        connect(&workerThread, &QThread::finished, &worker, &QObject::deleteLater);
    
        // 启动线程
        workerThread.start();
    
        // 等待线程结束
        workerThread.wait();
    
        return a.exec();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    在这个例子中,我们创建了一个Worker类,其中doWork槽函数包含我们要在新线程中执行的实际任务。我们将Worker对象移动到新创建的QThread中,并通过信号槽机制连接QThread::started信号和Worker::doWork槽函数。
    当调用workerThread.start()时,新线程的事件循环开始运行,进而触发Worker::doWork方法执行。
    注意,这里并没有直接在QThread的子类中重写run()函数来执行任务。

    4. 线程同步

    • Qt提供了多种线程同步原语,如 QMutexQWaitConditionQSemaphoreQReadWriteLock 等,用于解决多线程环境下的数据竞争和同步问题。
    #include 
    #include 
    #include 
    #include 
    
    class WorkerThread : public QThread
    {
        Q_OBJECT
    public:
        WorkerThread(QMutex* mutex, QWaitCondition* condition)
            : m_mutex(mutex), m_condition(condition) {}
    
    protected:
        void run() override
        {
            while (!shouldStop()) {
                // 请求资源
                m_mutex->lock();
    
                // 检查是否可以继续工作(此处仅为示例,实际场景可能更为复杂)
                if (canProceed()) {
                    processData();
                    emit dataProcessed();
                } else {
                    // 如果不能继续,则等待条件满足
                    m_condition->wait(m_mutex);
                }
    
                // 释放资源
                m_mutex->unlock();
            }
        }
    
    private:
        bool canProceed() const { /* 实现检查条件的方法 */ }
        void processData() { /* 执行需要同步的耗时操作 */ }
        bool shouldStop() const { /* 检查是否需要停止线程 */ }
    
    signals:
        void dataProcessed();
    
    private:
        QMutex* m_mutex;
        QWaitCondition* m_condition;
    };
    
    int main(int argc, char *argv[])
    {
        QApplication app(argc, argv);
    
        QMutex mutex;
        QWaitCondition condition;
    
        WorkerThread worker(&mutex, &condition);
        connect(&worker, &WorkerThread::dataProcessed, [](){
            qDebug() << "Data processed!";
        });
    
        worker.start();
    
        // 假设主线程在某一时刻改变了条件
        // ...
        mutex.lock();
        // 更新状态或数据
        condition.wakeOne();  // 唤醒等待的worker线程
        mutex.unlock();
    
        // 在适当时候停止worker线程...
    
        return app.exec();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    在这个例子中,WorkerThread类在一个循环中尝试获取互斥锁(QMutex),然后检查是否可以执行任务。如果条件不允许(例如,数据还没有准备好),则调用m_condition->wait(m_mutex),这会使线程进入等待状态,直到收到信(wakeOne()wakeAll())告知条件已改变。

    主线程可以通过锁定互斥锁,更新相关状态或数据,然后唤醒等待的线程,这样就实现了线程间的同步。当worker线程被唤醒后,会重新检查条件并执行相应任务。通过这种方式,可以有效地避免数据竞争问题,并确保线程在合适的时候执行操作。

    5. 注意事项

    • 避免直接在UI线程之外修改GUI组件,因为Qt的GUI必须在主线程中更新。若需在工作线程中操作UI,应使用信号槽机制或者其他线程安全的方式来通知主线程进行界面更新。
    • 主线程负责窗口事件处理(鼠标拖动/窗口缩放)或窗口控件数据更新,子线程不能对窗口对象做任何操作。
      (子线程没有权限对窗口做任何读写操作,必须把数据发送给主线程,由主线程操作)

    6. 最佳实践

    • 自Qt 4.8以来,推荐的做法是不直接继承 QThread 类,而是将要并发执行的任务封装到一个单独的QObject派生类中,让QThread负责运行这些对象的事件循环。
  • 相关阅读:
    【信号处理】基于EEG脑电信号的自闭症预测典型方法实现
    【Proteus仿真】【Arduino单片机】继电器和按键
    剑指offer 23. 反转链表
    2023-2028年中国硫氰酸钠市场发展动态及前景预测报告
    计算机网络-网络文件共享协议
    深度学习与总结JVM专辑(二):垃圾回收基础(图文+代码)
    uniapp起步
    AWS SAP-C02教程10-其它服务
    顶顶通呼叫中心中间件(mod_cti基于FreeSWITCH)-webrtc(浏览器直接拨打电话)
    第八章——权限管理与备份
  • 原文地址:https://blog.csdn.net/liyiran_/article/details/138043970