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()线程会停止,但是此函数存在一些不稳定的因素,不推荐使用)。 
#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

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

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

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

土拨鼠不是老鼠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值