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释放。
运行如图: