赞
踩
Qt 创建线程主要分为两种方式 :
1,继承自QThread,重写run函数,除run函数外,其它都在主线程中运行;
2,使用moveToThread将新建的线程移到继承QObject的新类实例中。
两种方法差不多,但是用QObject
来实现多线程有个非常好的优点,就是默认就支持事件循环(Qt的许多非GUI类也需要事件循环支持,如QTimer
、QTcpSocket
),QThread
要支持事件循环需要在QThread::run()
中调用QThread::exec()
来提供对消息循环的支持,否则那些需要事件循环支持的类都不能正常发送信号,因此如果要使用信号和槽,那就直接使用QObject
来实现多线程。
- #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线程结束才结束,还是这个线程只是临时生成,计算完就销毁。
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的类,对需要进行复杂耗时逻辑的入口函数声明为槽函数
- 此类在主线程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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。