目录
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中,而直接显式调用的槽函数仍运行在调用者的线程中(证据见以下实例)。
下面分别来说一下这两种使用方法:
参考: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的默认实现。
直接上例子:
//worker.h
#ifndef WORKER_H
#define WORKER_H
#include
#include
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
void Worker::showThreadId()
{
qDebug() << "Worker::showThreadId() = " << this->thread()->currentThreadId();
}
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
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(),那么该对象的所有槽函数就会在子线程执行(前提是,该槽函数是被信号触发的),如果直接显式调用这些槽函数,仍然会在运行在原线程,不会出现多线程的效果。
网上会看到很多这样的例子,继承QThread写完了子类MyThread,并重写run()之后,在run()调用MyThread类的槽函数或普通函数。这么写没有问题,只要在run中被调用,就能实现多线程执行。
很多人会把主线程的信号,绑定到MyThread的槽函数,期望主线程发信号时,MyThread的槽函数能够在子线程执行。结果打脸,并不能实现这种效果。因为MyThread只是在管理子线程,MyThread的对象本身仍属于原线程,所以这种情形下,MyThread的槽函数会在原线程被执行。
于是有人想出了这种办法,把MyThread的对象也弄到子线程里面去:在MyThread的构造函数中加上:this->moveToThread(this)。这样竟然真的就实现了上一段话中所提到的期望。
不过这一方法QT官方不推荐,因为从逻辑上、从代码上看都非常另类,MyThread的对象在原线程被创建,用于管理子线程,结果该对象又被移动到了子线程中。感觉这种方法以后肯定会被QT在编译环节给咔嚓掉。以后不要这么写就对了。
某对象一旦被放进了多线程,那么这个对象的所有槽函数中,或者run中,的代码段就必须要做线程同步防护,否则程序极大概率会发生崩溃。
线程同步常用的有:信号量、互斥锁等。