当前位置:   article > 正文

Qt 多线程创建_qt新建线程,运行结束之后自动删除

qt新建线程,运行结束之后自动删除

Qt 创建线程主要分为两种方式 :

1,继承自QThread,重写run函数,除run函数外,其它都在主线程中运行;

2,使用moveToThread将新建的线程移到继承QObject的新类实例中。

两种方法差不多,但是用QObject来实现多线程有个非常好的优点,就是默认就支持事件循环(Qt的许多非GUI类也需要事件循环支持,如QTimerQTcpSocket),QThread要支持事件循环需要在QThread::run()中调用QThread::exec()来提供对消息循环的支持,否则那些需要事件循环支持的类都不能正常发送信号,因此如果要使用信号和槽,那就直接使用QObject来实现多线程。

一:继承QThread方式创建线程

  1. 新建一个类 WorkThread,基类为QThread.
  2. 重写WorkThread的虚函数 void run(),然后调用函数WorkThread::start()后,则开启一条线程,自动运行函数run()
  3. 当停止线程时,添加一个bool变量,通过主线程修改这个bool变量来进行终止,但是需要对这个bool变量进行加锁处理(亲测, 主线程中调用wait(),quit(),线程都不会停止。terminate()线程会停止,但是此函数存在一些不稳定的因素,不推荐使用)。 
  1. #include <QCoreApplication>
  2. #include <QThread>
  3. #include <qDebug>
  4. #include <QMutex>
  5. class WorkThread : public QThread
  6. {
  7. Q_OBJECT
  8. public:
  9. WorkThread();
  10. void stop();
  11. protected:
  12. void run();
  13. private:
  14. QMutex m_lock;
  15. bool m_bCanRUn;
  16. };
  17. WorkThread::WorkThread()
  18. {
  19. }
  20. void WorkThread::stop()
  21. {
  22. QMutexLocker locker(&m_lock);
  23. m_bCanRUn=false;
  24. }
  25. void WorkThread::run()
  26. {
  27. while(true)
  28. {
  29. for(int n=0;n<10;n++)
  30. qDebug()<<n<<n<<n<<n<<n<<n<<n<<n;
  31. QMutexLocker locker(&m_lock); QMutexLocker可以安全的使用QMutex,以免忘记解锁(有点类似std::unique_ptr),这样每次循环都会看看是否要马上终止
  32. if(!m_bCanRUn){
  33. qDebug()<<"线程终止";
  34. return;
  35. }
  36. }
  37. }
  38. int main(int argc, char *argv[])
  39. {
  40. QCoreApplication a(argc, argv);
  41. WorkThread *thread1 = new WorkThread();
  42. thread1->start();
  43. QThread::msleep(3000);
  44. // thread1->wait(); //不会停止
  45. //thread1->quit(); //不会停止
  46. // thread1->terminate(); //会停止 但是不稳定
  47. thread1->stop(); //推荐使用
  48. thread1->wait()
  49. return 0;
  50. }

二,如何启动一个线程

          线程的启动简单分为两种,这两种方法的区分是由它父对象的归属和如何删除来决定的。首先要清除这个线程是否和主线程(UI线程)的生命周期一致,直到UI线程结束才结束还是这个线程只是临时生成,计算完就销毁。

  • 子线程创建时会把生成线程的窗体作为它的父对象,这样窗体结束时会自动析构线程的对象。但这是要注意一个问题,就是窗体结束时,子线程还未结束如何处理,如果没有处理这个问题,关闭窗口时,程序会崩溃,暂时把这种线程叫做全局线程,它在UI的生命周期中都存在。
  • 子线程是一种临时线程,一般用于一个大的计算,为了不让UI假死而触发的线程,这时需要注意一个问题,就是线程还没计算完,用户突然中止或者变更如何处理,这种线程更多见且容易出错,比如打开一个大的文件或者显示一个大图片,用户可能看一个大图片还没等图片处理完成又切换到下一个图片,这时子线程如何处理才能解决。暂时把这种线程叫局部线程,它在UI的生命周期中仅仅某时刻触发,然后销毁。这就涉及到如何终止正在执行的线程这个问题。

1,正确启动一个全局线程

  1. #ifndef WIDGET_H
  2. #define WIDGET_H
  3. #include <QThread>
  4. #include <qDebug>
  5. #include <QMutex>
  6. #include <QWidget>
  7. namespace Ui {
  8. class Widget;
  9. }
  10. class WorkThread : public QThread
  11. {
  12. Q_OBJECT
  13. public:
  14. explicit WorkThread(QObject* parent=0);
  15. void stop();
  16. protected:
  17. void run();
  18. private:
  19. QMutex m_lock;
  20. bool m_bCanRUn;
  21. };
  22. class Widget : public QWidget
  23. {
  24. Q_OBJECT
  25. public:
  26. explicit Widget(QWidget *parent = 0);
  27. ~Widget();
  28. private:
  29. Ui::Widget *ui;
  30. WorkThread* m_workThread;
  31. void startThread();
  32. };
  33. #endif // WIDGET_H

 

  1. #include "widget.h"
  2. #include "ui_widget.h"
  3. WorkThread::WorkThread(QObject *parent)
  4. {
  5. }
  6. void WorkThread::stop()
  7. {
  8. QMutexLocker locker(&m_lock);
  9. m_bCanRUn=false;
  10. }
  11. void WorkThread::run()
  12. {
  13. while(true)
  14. {
  15. for(int n=0;n<10;n++)
  16. qDebug()<<n<<n<<n<<n<<n<<n<<n<<n;
  17. QMutexLocker locker(&m_lock);
  18. if(!m_bCanRUn){
  19. qDebug()<<"线程终止";
  20. return;
  21. }
  22. }
  23. }
  24. Widget::Widget(QWidget *parent) :
  25. QWidget(parent),
  26. ui(new Ui::Widget)
  27. {
  28. ui->setupUi(this);
  29. //1 全局线程的创建
  30. //全局线程创建时可以把窗体指针作为父对象
  31. m_workThread = new WorkThread(this);
  32. startThread();
  33. }
  34. Widget::~Widget()
  35. {
  36. //3 退出线程
  37. m_workThread->stop();
  38. m_workThread->wait();
  39. delete ui;
  40. }
  41. void Widget::startThread()
  42. {
  43. //2 启动线程 重复调用start不会出现什么结果,但为了谨慎起见,还是建议在start之前进行判断
  44. if(m_workThread->isRunning()){
  45. return;
  46. }
  47. m_workThread->start();
  48. }
  •  由于是全局线程,因此在窗体创建时就创建线程,并把线程的父对象设置为窗体。这时需要注意不要手动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也是安全的。

 

  1. #ifndef WIDGET_H
  2. #define WIDGET_H
  3. #include <QThread>
  4. #include <qDebug>
  5. #include <QMutex>
  6. #include <QWidget>
  7. namespace Ui {
  8. class Widget;
  9. }
  10. class WorkThread : public QThread
  11. {
  12. Q_OBJECT
  13. public:
  14. explicit WorkThread(QObject* parent=0);
  15. ~WorkThread();
  16. void stop();
  17. protected:
  18. void run();
  19. private:
  20. QMutex m_lock;
  21. bool m_bCanRUn;
  22. };
  23. class Widget : public QWidget
  24. {
  25. Q_OBJECT
  26. public:
  27. explicit Widget(QWidget *parent = 0);
  28. ~Widget();
  29. private:
  30. Ui::Widget *ui;
  31. WorkThread* m_currentRunLoaclThread;
  32. public slots:
  33. void startThread();
  34. };
  35. #endif // WIDGET_H

 

  1. #include "widget.h"
  2. #include "ui_widget.h"
  3. WorkThread::WorkThread(QObject *parent)
  4. {
  5. }
  6. WorkThread::~WorkThread()
  7. {
  8. qDebug()<<"释放";
  9. }
  10. void WorkThread::stop()
  11. {
  12. QMutexLocker locker(&m_lock);
  13. m_bCanRUn=false;
  14. }
  15. void WorkThread::run()
  16. {
  17. while(true)
  18. {
  19. for(int n=0;n<10;n++){
  20. qDebug()<<n<<n<<n<<n<<n<<n<<n<<n;
  21. QThread::msleep(1000);
  22. }
  23. QMutexLocker locker(&m_lock);
  24. if(!m_bCanRUn){
  25. qDebug()<<"线程终止";
  26. return;
  27. }
  28. }
  29. }
  30. Widget::Widget(QWidget *parent) :
  31. QWidget(parent),
  32. ui(new Ui::Widget),m_currentRunLoaclThread(nullptr)
  33. {
  34. ui->setupUi(this);
  35. connect(ui->pushButton,SIGNAL(clicked(bool)),this,SLOT(startThread()));
  36. }
  37. Widget::~Widget()
  38. {
  39. delete ui;
  40. }
  41. void Widget::startThread()
  42. {
  43. //1 局部线程创建 这里父对象指定为NULL
  44. if(m_currentRunLoaclThread)
  45. {
  46. m_currentRunLoaclThread->stop(); //终结上次未执行完的线程,重新执行一个新线程
  47. qDebug()<<"stop";
  48. }
  49. WorkThread* workThread = new WorkThread(NULL);
  50. //2,线程结束后调用deleteLater来销毁分配的内存
  51. connect(workThread,&QThread::finished,workThread,&QObject::deleteLater);
  52. connect(workThread,&QObject::destroyed,[=](QObject *obj){
  53. if(qobject_cast<QObject*>(m_currentRunLoaclThread) == obj)
  54. {
  55. m_currentRunLoaclThread = nullptr;
  56. }
  57. });
  58. workThread->start();
  59. m_currentRunLoaclThread = workThread;
  60. }
  • 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()退出线程循环
  1. #ifndef WIDGET_H
  2. #define WIDGET_H
  3. #include <QThread>
  4. #include <qDebug>
  5. #include <QMutex>
  6. #include <QWidget>
  7. namespace Ui {
  8. class Widget;
  9. }
  10. class ThreadObject:public QObject{
  11. Q_OBJECT
  12. public:
  13. ThreadObject(QObject*parent =nullptr);
  14. ~ThreadObject();
  15. void stop();
  16. public slots:
  17. void runWork1();
  18. void runWork2();
  19. private:
  20. bool m_bStop;//stop函数不是通过信号槽触发,因此需要对数据进行保护,这里用了互斥锁对一个bool变量进行了保护处理,当然会失去一些性能 ,加锁后 下降1.5 倍
  21. QMutex m_stop;
  22. };
  23. class Widget : public QWidget
  24. {
  25. Q_OBJECT
  26. public:
  27. explicit Widget(QWidget *parent = 0);
  28. ~Widget();
  29. private:
  30. Ui::Widget *ui;
  31. ThreadObject* m_obj;
  32. QThread* m_objThread;
  33. public slots:
  34. void startThread();
  35. signals:
  36. void startObjThreadWork1();
  37. void startObjThreadWork2();
  38. };
  39. #endif // WIDGET_H

 

  1. #include "widget.h"
  2. #include "ui_widget.h"
  3. #include <iostream>
  4. using namespace std;
  5. Widget::Widget(QWidget *parent) :
  6. QWidget(parent),
  7. ui(new Ui::Widget),
  8. m_obj(nullptr),
  9. m_objThread(nullptr)
  10. {
  11. ui->setupUi(this);
  12. connect(ui->pushButton,&QPushButton::clicked,[=](){
  13. if(!m_objThread){
  14. startThread();
  15. }
  16. emit startObjThreadWork1();//主线程通过信号换起子线程的槽函数
  17. });
  18. connect(ui->pushButton_2,&QPushButton::clicked,[=](){
  19. if(!m_objThread){
  20. startThread();
  21. }
  22. emit startObjThreadWork2();//主线程通过信号换起子线程的槽函数
  23. });
  24. connect(ui->pushButton_3,&QPushButton::clicked,[=](){
  25. if(m_objThread){
  26. if(m_obj)
  27. m_obj->stop();
  28. }
  29. });
  30. }
  31. Widget::~Widget()
  32. {
  33. if(m_objThread){
  34. m_objThread->quit();
  35. }
  36. m_objThread->wait();
  37. delete ui;
  38. }
  39. void Widget::startThread()
  40. {
  41. if(m_objThread){
  42. return;
  43. }
  44. m_objThread= new QThread();
  45. m_obj = new ThreadObject();
  46. m_obj->moveToThread(m_objThread);
  47. connect(m_objThread,&QThread::finished,m_objThread,&QObject::deleteLater);
  48. connect(m_objThread,&QThread::finished,m_obj,&QObject::deleteLater);
  49. connect(this,&Widget::startObjThreadWork1,m_obj,&ThreadObject::runWork1);
  50. connect(this,&Widget::startObjThreadWork2,m_obj,&ThreadObject::runWork2);
  51. connect(m_objThread,&QObject::destroyed,[=](){
  52. m_objThread=nullptr;
  53. });
  54. connect(m_obj,&QObject::destroyed,[=](){
  55. m_obj=nullptr;
  56. });
  57. m_objThread->start();
  58. }
  59. ThreadObject::ThreadObject(QObject *parent):QObject(parent),
  60. m_bStop(true)
  61. {
  62. }
  63. ThreadObject::~ThreadObject()
  64. {
  65. }
  66. void ThreadObject::stop()
  67. {
  68. QMutexLocker locker(&m_stop);
  69. m_bStop=true;
  70. }
  71. void ThreadObject::runWork1()
  72. {
  73. {
  74. QMutexLocker locker(&m_stop);
  75. m_bStop=false;
  76. }
  77. while (1) {
  78. QMutexLocker locker(&m_stop);
  79. if(m_bStop)
  80. return;
  81. for(auto i=0;i<100;i++) {
  82. qDebug()<<i;
  83. }
  84. }
  85. }
  86. void ThreadObject::runWork2()
  87. {
  88. {
  89. QMutexLocker locker(&m_stop);
  90. m_bStop=false;
  91. }
  92. while (1) {
  93. QMutexLocker locker(&m_stop);
  94. if(m_bStop)
  95. return;
  96. for(auto i=101;i<200;i++) {
  97. qDebug()<<i;
  98. }
  99. }
  100. }

 

 

参考:https://blog.csdn.net/zong596568821xp/article/details/78893360

https://blog.csdn.net/li3781695/article/details/88233286

https://blog.51cto.com/9291927/1879757

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/104521
推荐阅读
相关标签
  

闽ICP备14008679号