Qt 创建线程主要分为两种方式 :
1,继承自QThread,重写run函数,除run函数外,其它都在主线程中运行;
2,使用moveToThread将新建的线程移到继承QObject的新类实例中。
两种方法差不多,但是用QObject
来实现多线程有个非常好的优点,就是默认就支持事件循环(Qt的许多非GUI类也需要事件循环支持,如QTimer
、QTcpSocket
),QThread
要支持事件循环需要在QThread::run()
中调用QThread::exec()
来提供对消息循环的支持,否则那些需要事件循环支持的类都不能正常发送信号,因此如果要使用信号和槽,那就直接使用QObject
来实现多线程。
一:继承QThread方式创建线程
- 新建一个类 WorkThread,基类为QThread.
- 重写WorkThread的虚函数 void run(),然后调用函数WorkThread::start()后,则开启一条线程,自动运行函数run()
- 当停止线程时,添加一个bool变量,通过主线程修改这个bool变量来进行终止,但是需要对这个bool变量进行加锁处理(亲测, 主线程中调用wait(),quit(),线程都不会停止。terminate()线程会停止,但是此函数存在一些不稳定的因素,不推荐使用)。
#include <QCoreApplication>
#include <QThread>
#include <qDebug>
#include <QMutex>
class WorkThread : public QThread
{
Q_OBJECT
public:
WorkThread();
void stop();
protected:
void run();
private:
QMutex m_lock;
bool m_bCanRUn;
};
WorkThread::WorkThread()
{
}
void WorkThread::stop()
{
QMutexLocker locker(&m_lock);
m_bCanRUn=false;
}
void WorkThread::run()
{
while(true)
{
for(int n=0;n<10;n++)
qDebug()<<n<<n<<n<<n<<n<<n<<n<<n;
QMutexLocker locker(&m_lock); QMutexLocker可以安全的使用QMutex,以免忘记解锁(有点类似std::unique_ptr),这样每次循环都会看看是否要马上终止
if(!m_bCanRUn){
qDebug()<<"线程终止";
return;
}
}
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
WorkThread *thread1 = new WorkThread();
thread1->start();
QThread::msleep(3000);
// thread1->wait(); //不会停止
//thread1->quit(); //不会停止
// thread1->terminate(); //会停止 但是不稳定
thread1->stop(); //推荐使用
thread1->wait()
return 0;
}
二,如何启动一个线程
线程的启动简单分为两种,这两种方法的区分是由它父对象的归属和如何删除来决定的。首先要清除这个线程是否和主线程(UI线程)的生命周期一致,直到UI线程结束才结束,还是这个线程只是临时生成,计算完就销毁。
- 子线程创建时会把生成线程的窗体作为它的父对象,这样窗体结束时会自动析构线程的对象。但这是要注意一个问题,就是窗体结束时,子线程还未结束如何处理,如果没有处理这个问题,关闭窗口时,程序会崩溃,暂时把这种线程叫做全局线程,它在UI的生命周期中都存在。
- 子线程是一种临时线程,一般用于一个大的计算,为了不让UI假死而触发的线程,这时需要注意一个问题,就是线程还没计算完,用户突然中止或者变更如何处理,这种线程更多见且容易出错,比如打开一个大的文件或者显示一个大图片,用户可能看一个大图片还没等图片处理完成又切换到下一个图片,这时子线程如何处理才能解决。暂时把这种线程叫局部线程,它在UI的生命周期中仅仅某时刻触发,然后销毁。这就涉及到如何终止正在执行的线程这个问题。
1,正确启动一个全局线程
#ifndef WIDGET_H
#define WIDGET_H
#include <QThread>
#include <qDebug>
#include <QMutex>
#include <QWidget>
namespace Ui {
class Widget;
}
class WorkThread : public QThread
{
Q_OBJECT
public:
explicit WorkThread(QObject* parent=0);
void stop();
protected:
void run();
private:
QMutex m_lock;
bool m_bCanRUn;
};
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
WorkThread* m_workThread;
void startThread();
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
WorkThread::WorkThread(QObject *parent)
{
}
void WorkThread::stop()
{
QMutexLocker locker(&m_lock);
m_bCanRUn=false;
}
void WorkThread::run()
{
while(true)
{
for(int n=0;n<10;n++)
qDebug()<<n<<n<<n<<n<<n<<n<<n<<n;
QMutexLocker locker(&m_lock);
if(!m_bCanRUn){
qDebug()<<"线程终止";
return;
}
}
}
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//1 全局线程的创建
//全局线程创建时可以把窗体指针作为父对象
m_workThread = new WorkThread(this);
startThread();
}
Widget::~Widget()
{
//3 退出线程
m_workThread->stop();
m_workThread->wait();
delete ui;
}
void Widget::startThread()
{
//2 启动线程 重复调用start不会出现什么结果,但为了谨慎起见,还是建议在start之前进行判断
if(m_workThread->isRunning()){
return;
}
m_workThread->start();
}
- 由于是全局线程,因此在窗体创建时就创建线程,并把线程的父对象设置为窗体。这时需要注意不要手动delete线程指针。由于你的QThread是在Qt的事件循环里,手动delete会发生不可预料的意外。理论上所有QObject都不应该手动delete。
- 如果确认要删除,请使用deleteLater(),此函数尤其对局部线程有用。
- wait()这一句是在主线程等待子线程结束才能继续往下执行,这样能确保过程是单向的,也就是不会说子线程还没结束,主线程就析构,因此wait的作用就是挂起,一直等到子线程结束。
- 还有一种方法是让QThread自己删除自己,就是在new线程时,不指定父对象,通过绑定**
void QObject::deleteLater () [slot]
**槽让它自动释放。这样在widget析构时可以免去m_thread->wait()
这句
2,正确启动一个局部线程
启动一个局部线程(就是运行完自动删除的线程)方法和启动全局线程差不多,但要关联多一个槽函数,就是之前提到的**void QObject::deleteLater () [slot]
**,这个槽函数是能安全释放线程资源的关键(直接delete thread指针不安全)。
- QObject::deleteLater()并没有将对象立即销毁,而是向主消息循环发送了一个event,下一次主消息循环收到这个event之后才会销毁对象。 这样做的好处是可以在这些延迟删除的时间内完成一些操作,坏处就是内存释放会不及时。
- Qt中不建议手动delete掉QObject对象
原因一:不注意父子关系会导致某个对象析构两次,一次是手动析构,还有一次是parent析构,后者可能会出现delete堆上的对象。
原因二:删除一个pending events等待传递的QObject会导致崩溃,所以不能直接跨线程删除对象,而QObject析构函数会断开所有信号和槽,因此用deleteLater代替比较好,它会让所有事件都发送完一切处理好后马上清除这片内存,而且就算调用多次的deletelater也是安全的。
#ifndef WIDGET_H
#define WIDGET_H
#include <QThread>
#include <qDebug>
#include <QMutex>
#include <QWidget>
namespace Ui {
class Widget;
}
class WorkThread : public QThread
{
Q_OBJECT
public:
explicit WorkThread(QObject* parent=0);
~WorkThread();
void stop();
protected:
void run();
private:
QMutex m_lock;
bool m_bCanRUn;
};
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
WorkThread* m_currentRunLoaclThread;
public slots:
void startThread();
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
WorkThread::WorkThread(QObject *parent)
{
}
WorkThread::~WorkThread()
{
qDebug()<<"释放";
}
void WorkThread::stop()
{
QMutexLocker locker(&m_lock);
m_bCanRUn=false;
}
void WorkThread::run()
{
while(true)
{
for(int n=0;n<10;n++){
qDebug()<<n<<n<<n<<n<<n<<n<<n<<n;
QThread::msleep(1000);
}
QMutexLocker locker(&m_lock);
if(!m_bCanRUn){
qDebug()<<"线程终止";
return;
}
}
}
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget),m_currentRunLoaclThread(nullptr)
{
ui->setupUi(this);
connect(ui->pushButton,SIGNAL(clicked(bool)),this,SLOT(startThread()));
}
Widget::~Widget()
{
delete ui;
}
void Widget::startThread()
{
//1 局部线程创建 这里父对象指定为NULL
if(m_currentRunLoaclThread)
{
m_currentRunLoaclThread->stop(); //终结上次未执行完的线程,重新执行一个新线程
qDebug()<<"stop";
}
WorkThread* workThread = new WorkThread(NULL);
//2,线程结束后调用deleteLater来销毁分配的内存
connect(workThread,&QThread::finished,workThread,&QObject::deleteLater);
connect(workThread,&QObject::destroyed,[=](QObject *obj){
if(qobject_cast<QObject*>(m_currentRunLoaclThread) == obj)
{
m_currentRunLoaclThread = nullptr;
}
});
workThread->start();
m_currentRunLoaclThread = workThread;
}
new ThreadFromQThread(NULL);
并没有给他指定父对象connect(thread,&QThread::finished ,thread,&QObject::deleteLater);
线程结束后调用deleteLater来销毁分配的内存。 再线程运行完成,发射finished信号后会调用deleteLater
函数,在确认消息循环中没有这个线程的对象后会销毁。对于一些需求,线程开启后再点击按钮不会再重新生成线程,一直等到当前线程执行完才能再次点击按钮,这种情况很好处理,加个标记就可以实现,也一般比较少用。另外更多见的需求是,再次点击按钮,需要终结上次未执行完的线程,重新执行一个新线程。这种情况非常多见,例如一个普通的图片浏览器,都会有下一张图和上一张图这种按钮,浏览器加载图片一般都在线程里执行(否则点击超大图片时图片浏览器会类似卡死的状态),用户点击下一张图片时需要终止正在加载的当前图片,加载下一张图片。你不能要求客户要当前图片加载完才能加载下一张图片,这就几乎沦为单线程了。这时候,就需要终止当前线程,开辟新线程加载下一个图片。这里用一个临时变量记录当前正在运行的局部线程,由于线程结束时会销毁自己,因此要通知主线程把这个保存线程指针的临时变量设置为NULL 因此用到了
QObject::destroyed
信号,在线程对象析构时通知UI把m_currentRunLoaclThread
设置为nullptr;
二:继承QObject的多线程使用方法
- 写一个继承QObject的类,对需要进行复杂耗时逻辑的入口函数声明为槽函数
- 此类在主线程new出来,不给它设置任何父对象
- 同时声明一个QThread对象
- 把obj通过moveToThread方法转移到新线程中,此时object已经在线程中了
- 把线程的finish信号和object的deleteLater槽连接,这个信号槽必须连接,否则会内存泄漏
- 正常连接其它信号和槽(在连接信号和槽之前调用moveToThread,不需要处理connect的第五个参数,否则就显示声明用Qt::QueuedConnection来连接)
- 初始化完成后调用QThread::start()来启动线程
- 逻辑结束后,调用QThread::quit()退出线程循环
#ifndef WIDGET_H
#define WIDGET_H
#include <QThread>
#include <qDebug>
#include <QMutex>
#include <QWidget>
namespace Ui {
class Widget;
}
class ThreadObject:public QObject{
Q_OBJECT
public:
ThreadObject(QObject*parent =nullptr);
~ThreadObject();
void stop();
public slots:
void runWork1();
void runWork2();
private:
bool m_bStop;//stop函数不是通过信号槽触发,因此需要对数据进行保护,这里用了互斥锁对一个bool变量进行了保护处理,当然会失去一些性能 ,加锁后 下降1.5 倍
QMutex m_stop;
};
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
ThreadObject* m_obj;
QThread* m_objThread;
public slots:
void startThread();
signals:
void startObjThreadWork1();
void startObjThreadWork2();
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
#include <iostream>
using namespace std;
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget),
m_obj(nullptr),
m_objThread(nullptr)
{
ui->setupUi(this);
connect(ui->pushButton,&QPushButton::clicked,[=](){
if(!m_objThread){
startThread();
}
emit startObjThreadWork1();//主线程通过信号换起子线程的槽函数
});
connect(ui->pushButton_2,&QPushButton::clicked,[=](){
if(!m_objThread){
startThread();
}
emit startObjThreadWork2();//主线程通过信号换起子线程的槽函数
});
connect(ui->pushButton_3,&QPushButton::clicked,[=](){
if(m_objThread){
if(m_obj)
m_obj->stop();
}
});
}
Widget::~Widget()
{
if(m_objThread){
m_objThread->quit();
}
m_objThread->wait();
delete ui;
}
void Widget::startThread()
{
if(m_objThread){
return;
}
m_objThread= new QThread();
m_obj = new ThreadObject();
m_obj->moveToThread(m_objThread);
connect(m_objThread,&QThread::finished,m_objThread,&QObject::deleteLater);
connect(m_objThread,&QThread::finished,m_obj,&QObject::deleteLater);
connect(this,&Widget::startObjThreadWork1,m_obj,&ThreadObject::runWork1);
connect(this,&Widget::startObjThreadWork2,m_obj,&ThreadObject::runWork2);
connect(m_objThread,&QObject::destroyed,[=](){
m_objThread=nullptr;
});
connect(m_obj,&QObject::destroyed,[=](){
m_obj=nullptr;
});
m_objThread->start();
}
ThreadObject::ThreadObject(QObject *parent):QObject(parent),
m_bStop(true)
{
}
ThreadObject::~ThreadObject()
{
}
void ThreadObject::stop()
{
QMutexLocker locker(&m_stop);
m_bStop=true;
}
void ThreadObject::runWork1()
{
{
QMutexLocker locker(&m_stop);
m_bStop=false;
}
while (1) {
QMutexLocker locker(&m_stop);
if(m_bStop)
return;
for(auto i=0;i<100;i++) {
qDebug()<<i;
}
}
}
void ThreadObject::runWork2()
{
{
QMutexLocker locker(&m_stop);
m_bStop=false;
}
while (1) {
QMutexLocker locker(&m_stop);
if(m_bStop)
return;
for(auto i=101;i<200;i++) {
qDebug()<<i;
}
}
}
参考:https://blog.csdn.net/zong596568821xp/article/details/78893360