《QThread源码浅析》
子类化QThread来实现多线程, QThread只有run函数是在新线程里的,其他所有函数都在QThread生成的线程里。正确启动线程的方法是调用QThread::start()来启动,如果直接调用run成员函数,这个时候并不会有新的线程产生( 原因: 可以查看往期《QThread源码浅析》文章,了解下run函数是怎么被调用的)。
一、步骤子类化 QThread;
重写run,将耗时的事件放到此函数执行;
根据是否需要事件循环,若需要就在run函数中调用 QThread::exec() ,开启线程的事件循环。事件循环的作用可以查看往期《QThread源码浅析》文章中《QThread::run()源码》小节进行阅读;
为子类定义信号和槽,由于槽函数并不会在新开的线程运行,所以需要在构造函数中调用 moveToThread(this)。 注意:虽然调用moveToThread(this)可以改变对象的线程依附性关系,但是QThread的大多数成员方法是线程的控制接口,QThread类的设计本意是将线程的控制接口供给旧线程(创建QThread对象的线程)使用。所以不要使用moveToThread()将该接口移动到新创建的线程中,调用moveToThread(this)被视为不好的实现。
接下来会通过 使用线程来实现计时器,并实时在UI上显示 的实例来说明不使用事件循环和使用事件循环的情况。(此实例使用QTimer会更方便,此处为了说明QThread的使用,故使用线程来实现)
二、不使用事件循环实例InheritQThread.hpp
class InheritQThread:public QThread { Q_OBJECT public: InheritQThread(QObject *parent = Q_NULLPTR):QThread(parent){ } void StopThread(){ QMutexLocker lock(&m_lock); m_flag = false; } protected: //线程执行函数 void run(){ qDebug()<<"child thread = "<<QThread::currentThreadId(); int i=0; m_flag = true; while(1) { ++i; emit ValueChanged(i); //发送信号不需要事件循环机制 QThread::sleep(1); { QMutexLocker lock(&m_lock); if( !m_flag ) break; } } } signals: void ValueChanged(int i); public: bool m_flag; QMutex m_lock; };mainwindow.hpp
class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr) : QMainWindow(parent), ui(new Ui::MainWindow){ ui->setupUi(this); qDebug()<<"GUI thread = "<<QThread::currentThreadId(); WorkerTh = new InheritQThread(this); connect(WorkerTh, &InheritQThread::ValueChanged, this, &MainWindow::setValue); } ~MainWindow(){ delete ui; } public slots: void setValue(int i){ ui->lcdNumber->display(i); } private slots: void on_startBt_clicked(){ WorkerTh->start(); } void on_stopBt_clicked(){ WorkerTh->StopThread(); } void on_checkBt_clicked(){ if(WorkerTh->isRunning()){ ui->label->setText("Running"); }else{ ui->label->setText("Finished"); } } private: Ui::MainWindow *ui; InheritQThread *WorkerTh; };在使用多线程的时候,如果出现共享资源使用,需要注意资源抢夺的问题,例如上述InheritQThread类中m_flag变量就是一个多线程同时使用的资源,上面例子使用 QMutexLocker+QMutex 的方式对临界资源进行安全保护使用,其实际是使用了 RAII技术:(Resource Acquisition Is Initialization),也称为“资源获取就是初始化”,是C++语言的一种管理资源、避免泄漏的惯用法。C++标准保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。简单的说,RAII 的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终保持有效,最后在对象析构的时候释放资源。具体 QMutexLocker+QMutex 互斥锁的原理以及使用方法,在这里就不展开说了,这个知识点网上有很多非常好的文章。
效果:
(1)在不点【start】按键的时候,点击【check thread state】按钮检查线程状态,该线程是未开启的。
(2)按下【start】后效果如下,并查看终端消息打印信息: