QT多线程QThread::run()与QObject::moveToThread()标准用法

目录

1、使用QThread::run()

2、使用QObject::moveToThread

3、常见的错误编程方法

4、注意事项


 

QT实现多线程有两种方法:

1、继承QThread类,并重写run()函数;
---------这样run()中的代码就会运行在子线程中。

2、①写一个对象worker,②声明或new一个QThread变量mythread,③把这个对象移动到子线程中:worker.moveToThread(&mythread),④mythread.start()。其中①②不分先后, ③④不分先后。
---------这样被信号触发的worker对象的槽函数就会运行在子线程thread中,而直接显式调用的槽函数仍运行在调用者的线程中(证据见以下实例)。

 

下面分别来说一下这两种使用方法:

1、使用QThread::run()

参考:https://www.cnblogs.com/wangshaowei/p/8384474.html

https://blog.csdn.net/weixin_33716557/article/details/93720605

QT官方教程《Starting Threads with QThread》里这样说:https://doc.qt.io/archives/qt-4.8/threads-starting.html,

我们知道:每一个QThread对象都管理着一个线程,并通过start函数启动这个线程,线程要执行的代码都在run()里面。run函数对一个线程来说,就好比main函数对一个应用程序。run函数的进入和返回,就相当于:子线程的启动和结束。

run函数何时返回?一般来说,有3种常见的情形:

①run中的代码走完一遍就返回了;
②run中有while(1)大循环,在大循环中有退出循环的flag变量,由外部程序置位这个flag,使大循环退出,从而run()返回;
③run的最后一行是事件循环exec(),也即程序会卡在这一行:this->exec();  这种情况需要主动调用或者信号调用QThread::finish()或terminate()才能让事件循环停下。从而让run()返回。

实际编程时,到底用哪一种,看个人需求。

以①为例:

//.h
class Worker : public QThread
{
public:
    Worker(){;}    

public slots:
    void showMsg(void);

protected:
    void run();
};

//.cpp
void Worker::showMsg()
{
    qDebug() << "Worker::showMsg() thread id = " << this->currentThreadId();
}

void Worker::run()
{
    this->showMsg();
}

运行结果可见:this->showMsg()这行代码执行时,所在的线程与主线程不同。这就说明多线程实验成功了。

需要注意的是,只有run()函数中的代码段,会在子线程执行,如果在主线程直接调用(或者用信号触发)某个MyThread对象的showMsg()函数,打印出的线程ID仍然是主线程的ID,并不能实现多线程的效果。

再补充一句:QThread类的run()的默认实现是这样的:QThread::run()  {  this->exec();  },   上述用法③实际上就是在仿照QThread的默认实现。

 

2、使用QObject::moveToThread

直接上例子:

//worker.h
#ifndef WORKER_H
#define WORKER_H

#include <QObject>
#include <QThread>
class Worker : public QObject
{
    Q_OBJECT
public:
    explicit Worker(QObject *parent = nullptr){;}

signals:

public slots:
    void showThreadId(void);
};

#endif // WORKER_H
//worker.cpp
#include "worker.h"
#include <qDebug>

void Worker::showThreadId()
{
    qDebug() << "Worker::showThreadId() = " << this->thread()->currentThreadId();
}
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <qDebug>
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    worker = new Worker();
    //connect(this, &MainWindow::showCmd, worker, &Worker::showThreadId);//位置1
    childThread = new QThread();
    childThread->start();//一定不要忘记启动事件循环
    worker->moveToThread(childThread);
    //connect(this, &MainWindow::showCmd, worker, &Worker::showThreadId);//位置2

    qDebug() << "main thread = " << this->thread()->currentThreadId();

    worker->showThreadId();//主线程直接调用 worker->showThreadId()
    emit showCmd();//主线程信号触发 worker->showThreadId()
    //注意看以上两行代码运行结果的区别
}

运行结果:

由以上结果可见,任何对象只要执行了moveToThread(),那么该对象的所有槽函数就会在子线程执行(前提是,该槽函数是被信号触发的),如果直接显式调用这些槽函数,仍然会在运行在原线程,不会出现多线程的效果。

 

3、常见的错误编程方法

网上会看到很多这样的例子,继承QThread写完了子类MyThread,并重写run()之后,在run()调用MyThread类的槽函数或普通函数。这么写没有问题,只要在run中被调用,就能实现多线程执行。

很多人会把主线程的信号,绑定到MyThread的槽函数,期望主线程发信号时,MyThread的槽函数能够在子线程执行。结果打脸,并不能实现这种效果。因为MyThread只是在管理子线程,MyThread的对象本身仍属于原线程,所以这种情形下,MyThread的槽函数会在原线程被执行。

于是有人想出了这种办法,把MyThread的对象也弄到子线程里面去:在MyThread的构造函数中加上:this->moveToThread(this)。这样竟然真的就实现了上一段话中所提到的期望。

不过这一方法QT官方不推荐,因为从逻辑上、从代码上看都非常另类,MyThread的对象在原线程被创建,用于管理子线程,结果该对象又被移动到了子线程中。感觉这种方法以后肯定会被QT在编译环节给咔嚓掉。以后不要这么写就对了。

4、注意事项

某对象一旦被放进了多线程,那么这个对象的所有槽函数中,或者run中,的代码段就必须要做线程同步防护,否则程序极大概率会发生崩溃。

线程同步常用的有:信号量、互斥锁等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章