1. 为什么要使用多线程
通常情况下,应用程序都是在一个线程中执行操作。但是,当调用一个耗时操作(例如,大批量I/O或大量矩阵变换等CPU密集操作)时,用户界面常常会冻结。而使用多线程可以解决这一问题。
2. 多线程有以下几个优势:
- 提高应用程序响应速度。
这对于图形界面开发的程序尤为重要,当一个操作耗时很长时,整个系统都会等待这个操作,程序就不能响应键盘、鼠标、菜单等操作,而使用多线程技术可将耗时长的操作置于一个新的线程,避免以上问题。 - 使多CPU系统更加有效。
当前线程数不大于CPU数目时,操作系统可以调度不同的线程运行于不同的CPU上。 - 改善程序结构
一个既长又复杂的进程可以考虑分为多个线程,成为独立或半独立的运行部分,这样有利于代码的理解和维护。
3. 多线程程序有以下几个特点
- 多线程程序的行为无法预期,当多次执行程序时,每一次的结果都可能不同。
- 多线程的执行顺序无法保证,它与操作系统的调度策略和线程优先级等因素有关。
- 多线程的切换可能发生在任何时刻、任何地点。
- 多线程对代码的敏感度高,对代码的细微修改都可能产生意想不到的结果。
基于以上这些特点,为了有效的使用线程,开发人员必须对其进行控制。
4. 线程的实现
Qt有两种多线程的方法,一种是继承QThread的run函数;另外一种是把一个继承于QObject的类转移到一个Thread里。
4.1 线程实现方法一(继承QThread
)
直接来一个实例,实例主要是实现了在一个定时器开始之前,执行了一些复杂的数据计算(这里是直接sleep 五秒)
mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QObject>
#include <QThread>
class MyThread : public QThread
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = nullptr);
protected:
//QThread的虚函数
//线程处理函数
//不能直接调用,通过start()间接调用
void run();
signals:
void isDone();//线程任务完成,定时器停止信号。
public slots:
};
#endif // MYTHREAD_H
mywidget.h
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include "mythread.h"
#include <QWidget>
#include <QTimer>//定时器头文件
namespace Ui {
class MyWidget;
}
class MyWidget : public QWidget
{
Q_OBJECT
public:
explicit MyWidget(QWidget *parent = 0);
void dealTimeout();//定时器槽函数
void dealDone();//线程槽函数
void stopThread();//停止线程
~MyWidget();
private slots:
void on_pushButton_clicked();
private:
Ui::MyWidget *ui;
QTimer *myTimer;//声明变量
MyThread *thread;//线程对象
};
#endif // MYWIDGET_H
mythread.cpp
#include "mythread.h"
MyThread::MyThread(QObject *parent) : QThread(parent)
{
}
void MyThread::run()
{
//很复杂的数据处理,需要耗时5秒
sleep(5);
emit isDone();
}
mywidget.cpp
#include "mywidget.h"
#include "ui_mywidget.h"
#include <QThread>
#include <QDebug>
MyWidget::MyWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::MyWidget)
{
ui->setupUi(this);
myTimer = new QTimer(this);
//只要定时器启动,就会自动触发timeout()
connect(myTimer,&QTimer::timeout,this,&MyWidget::dealTimeout);
//分配空间
thread = new MyThread(this);
connect(thread,&MyThread::isDone,this,&MyWidget::dealDone);
//当按窗口左上角x时候,窗口触发destroyed()
connect(this,&MyWidget::destroyed,this,&MyWidget::stopThread);
}
void MyWidget::stopThread()
{
//停止线程
thread->quit();
//等待线程处理完手头的工作
thread->wait();
}
void MyWidget::dealDone()
{
qDebug()<<"it is over";
myTimer->stop();//关闭定时器
}
void MyWidget::dealTimeout()
{
static int i = 0;
i++;
//设置lcd的值
ui->lcdNumber->display(i);
}
MyWidget::~MyWidget()
{
delete ui;
}
void MyWidget::on_pushButton_clicked()
{
//如果定时器没有工作,才能启动线程
if(myTimer->isActive() == false)
{
myTimer->start(1000);//时间间隔为1秒
}
//启动线程,处理数据
thread->start();
}
运行结果如图所示:五秒之后定时器停止。
注意:
QThread
只有run
函数是在新线程里的,其他所有函数都在QThread
生成的线程里run
不能直接调用,通过start()间接调用
4.2 线程实现方法二
主要思路:
mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QObject>
class MyThread : public QObject
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = nullptr);
//线程处理函数
void myTimeout();
void setFlag(bool flag = true);
signals:
void mySignal();
public slots:
private:
bool isStop;
};
#endif // MYTHREAD_H
mywidget.h
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QWidget>
#include "mythread.h"
#include <QThread>
namespace Ui {
class myWidget;
}
class myWidget : public QWidget
{
Q_OBJECT
public:
explicit myWidget(QWidget *parent = 0);
~myWidget();
void dealSignal();
void dealClose();
private slots:
void on_pushButtonStart_clicked();
void on_pushButton_2_clicked();
signals:
void startThread();//启动子线程的信号
private:
Ui::myWidget *ui;
MyThread *myT;
QThread *thread;
};
#endif // MYWIDGET_H
mythread.cpp
#include "mythread.h"
#include <QThread>
#include <QDebug>
//#include <QMessageBox>
MyThread::MyThread(QObject *parent) : QObject(parent)
{
isStop = false;
}
void MyThread::myTimeout()
{
while(isStop == false)
{
QThread::sleep(1);
emit mySignal();
// QMessageBox::aboutQt(NULL);
qDebug()<<"子线程号:"<<QThread::currentThread();
if(true == isStop)
{
break;
}
}
}
void MyThread::setFlag(bool flag)
{
isStop = flag;
}
mywidget.cpp
#include "mywidget.h"
#include "ui_mywidget.h"
#include <QDebug>
myWidget::myWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::myWidget)
{
ui->setupUi(this);
//动态分配空间,不能指定父对象
myT = new MyThread();
//创建子线程
thread = new QThread(this);
//把自定义的线程加入到子线程中
myT->moveToThread(thread);
connect(myT,&MyThread::mySignal,this,&myWidget::dealSignal);
qDebug()<<"主线程号:"<<QThread::currentThread();
connect(this,&myWidget::startThread,myT,&MyThread::myTimeout);
connect(this,&myWidget::destroyed,this,&myWidget::dealClose);
//线程处理函数内部,不允许操作图形界面
//connect()第五个参数的作用,连接方式:默认,队列,直接
//只有在多线程的时候才有意义
//如果是多线程,默认是用队列
//如果是单线程,默认使用直接方式
//队列:槽函数所在的线程和接收者一样
//直接:槽函数所在线程和发送者一样
}
myWidget::~myWidget()
{
delete ui;
}
void myWidget::dealClose()
{
on_pushButton_2_clicked();
delete myT;//释放内存
}
void myWidget::dealSignal()
{
static int i = 0;
i++;
ui->lcdNumber->display(i);
}
void myWidget::on_pushButtonStart_clicked()
{
if(thread->isRunning() == true)
{
return;
}
//启动线程,但是没有启动线程处理函数
thread->start();
myT->setFlag(false);
//不能直接调用线程处理函数,
//直接调用导致线程处理函数和主线程是在同一个线程
// myT->myTimeout();
//只能通过signal-slot方式调用
emit startThread();
}
//关闭
void myWidget::on_pushButton_2_clicked()
{
if(thread->isRunning() == false)
{
return;
}
myT->setFlag(true);
thread->quit();
thread->wait();
}
【参考博客】
Qt使用多线程的一些心得
示例代码(day05 文件夹下面):
https://github.com/zhenyusu/qt_study_project02.git