QT多线程的使用

风流意气都作罢 提交于 2020-01-15 04:45:22

Qt中提供了对于线程的支持,它提供了一些独立于平台的线程类,要进行多线程方法,可以有两种方式。


1. 第一种方式

qt提供QThread类,在QThread类中有一个virtual函数QThread::run()。

要创建一个新的线程,我们只需定义一个MyThread类,让其继承QThread,然后重新实现QThread::run()。

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>

class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = 0);
    ~MyThread();

protected:
    //QThread的虚函数
    //线程处理函数
    //不能直接调用,通过start()间接调用
    void run();

signals:
    void isDone();  //处理完成信号

signals:

public slots:
};

#endif // MYTHREAD_H

};

然后可以在run中写入要进行的操作,比如可以让其等待5秒。若不是多线程,在运行时我们单击窗口,窗口会出现无响应的状态。

那如何通知线程结束?这就可以用qt的信号和槽机制了,我们可以在操作完成时发出一个完成信号,完成信号我们在声明文件里已经定义了。

void MyThread::run()
{
    //很复杂的数据处理
    //需要耗时5秒
    sleep(5);

    emit isDone();  //发送完成信号
}

这样,我们就把线程的操作写完了。

现在,我们先来布一个简单的ui,只用到了一个LcdNumber和一个PushButton。

在当前widget的头文件中定义一些需要用到的操作。并加入我们定义的线程文件。

#ifndef MYWIDGET_H
#define MYWIDGET_H

#include <QWidget>
#include <QTimer>
#include "mythread.h"  //线程头文件


namespace Ui {
class MyWidget;
}

class MyWidget : public QWidget
{
    Q_OBJECT

public:
    explicit MyWidget(QWidget *parent = 0);
    ~MyWidget();

    void dealTimeout();  //定时器槽函数
    void dealDone();   //线程槽函数
    void stopThread();  //停止线程

private slots:
    void on_pushButton_clicked();

private:
    Ui::MyWidget *ui;

    QTimer *myTimer;  //声明变量
    MyThread *thread;  //线程对象
};

#endif // MYWIDGET_H

我们用一个timer定时器来让Lcd控件按指定时间更新数字,当我们点击开始按钮时,定时器启动,自动触发timerout信号。捕获timerout信号,在dealTimerout()函数中写入需要进行的操作,当捕获到timerout时,自动使用dealTimerout槽函数。

dealTimerout()我们可以这样写:

void MyWidget::dealTimeout()
{
    static int i = 0;
    i++;
    //设置lcd的值
    ui->lcdNumber->display(i);
}

在widget的构造函数中,先创建一个定时器并为线程函数分配空间:

myTimer = new QTimer(this);

thread = new QThread(thread);  //分配空间

然后我们关联信号和槽:

//只要定时器启动,自动触发timeout
connect(myTimer, &QTimer::timeout, this, &MyWidget::dealTimeout);

在开始按钮的槽函数中,启动定时器,并开启线程,需要注意的是,我们不能直接调用run函数,要通过start()间接调用线程函数。

void MyWidget::on_pushButton_clicked()
{
    //若定时器没有工作
    if(myTimer->isActive() == false)
    {
        myTimer->start(100);
    }

    //启动线程,处理数据
    thread->start();
}

线程结束时我们接收到isDone信号,我们在其中关闭定时器:

void MyWidget::dealDone()
{
    qDebug() << "it is over";  //打印线程结束信息
    myTimer->stop();  //关闭定时器
}

我们选择在退出窗口时关闭线程,退出窗口时会触发destroyed信号,线程关闭的槽函数实现如下:

void MyWidget::stopThread()
{
    //停止线程
    thread->quit();

    //等待线程处理完手头工作
    thread->wait();
}

最后,在widget主窗口的构造函数中加入线程的关联信号和槽:

connect(thread, &MyThread::isDone, this, &MyWidget::dealDone);

//当按窗口右上角x时,触发destroyed信号
connect(this, &MyWidget::destroyed, this, &MyWidget::stopThread);

现在,当我们启动程序时,窗口如下:

当我们点击Start按钮时,触发定时器,开启线程,每100ms更新一次Lcd中的数值:

5秒后,线程停止,发出isDone信号,执行dealDone槽函数,显示it is over并关闭计时器:

再次点击,再次启动定时器,继续累加数字并设置到Lcd中,点击x,程序退出,停止线程。


2. 第二种方式

新建一个工程,ui如图:

新建一个类,继承自QObject,然后在类中设置一个线程函数。

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QObject>

class MyThread : public QObject
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = 0);
    ~MyThread();

    //线程处理函数
    void MyTimeout();

    void setFlag(bool flag = true);


signals:
    void mySignal();

public slots:

private:
    bool isStop;
};

#endif // MYTHREAD_H

通过一个bool变量来控制线程结束,通过发出mySignal()信号来调用处理槽函数。

在widget中定义如下:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include "mythread.h"
#include <QThread>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

    void dealSignal();
    void dealClose();

signals:
    void startThread();  //启动子线程的信号

private slots:
    void on_pushButtonStart_clicked();

    void on_pushButtonStop_clicked();

private:
    Ui::Widget *ui;

    MyThread *myT;
    QThread *thread;
};

#endif // WIDGET_H

两个槽函数dealSignal()和dealClose()分别关联着mySignal()和destroyed()信号。

信号startThread()用于启动子线程。

在widget的实现中,


  • 首先创建一个线程对象,需要注意不能指定父对象。
    myT = new MyThread;

  • 创建一个Qthread子线程对象
    QThread *thread = new QThread(this);

  • 把我们的自定义线程类,加入到子线程(若是myT指定了父对象,此处就会出错。)
    my->moveToThread(thread);

  • 启动子线程,只是把线程开启了,并没有启动线程处理函数
    thread.start();

  • 线程的启动,必须通过signal - slot的方式。

各种函数实现如下:

void Widget::dealSignal()
{
    static int i = 0;
    i++;
    ui->lcdNumber->display(i);
}

//Start按钮
void Widget::on_pushButtonStart_clicked()
{
    if(thread->isRunning() == true)
    {
        return;
    }

    //启动线程,但是没有启动线程处理函数
    thread->start();
    myT->setFlag(false);

    //不能直接调用线程处理函数,直接调用导致线程处理函数和主线程在同一个线程
    //myT->MyTimeout();

    //只能通过 signal - slot方式
    emit startThread();
}

//Stop按钮
void Widget::on_pushButtonStop_clicked()
{
    if(thread->isRunning() == false)
    {
        return;
    }

    myT->setFlag(true);
    thread->quit();
    thread->wait();
}

void Widget::dealClose()
{
    on_pushButtonStop_clicked();
    delete myT;
}

dealSignal中,使Lcd数字递增。

当按下Start按钮,启动线程,设置线程标志为flase,通过发出startThread()信号来调用真正的线程函数MyThread::MyTimeout。

当按下Stop按钮时,设置线程标志为true,关闭线程。

由于未给myT指定父对象,所以需要我们手动来释放内存,当点击x时,关闭线程,delete释放。

运行如图:

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!