一、多线程介绍
通常情况下,应用程序都是在一个线程中执行操作。但是当调用一个耗时操作(例如,大批量I/O或大量矩阵变换等CPU密集操作)时,用户界面常常会冻结。而使用多线程可以解决这一问题。
多线程的优势:
1. 提高应用程序响应速度
当一个操作耗时很长时,整个系统都会等待这个操作,程序就不能响应键盘、鼠标、菜单等操作,而使用多线程技术可将耗时长的操作置于一个新的线程,避免以上问题。
2. 使多CPU系统更加有效
当前线程数不大于CPU数目时,操作系统可以调度不同的线程运行于不同的CPU上。
3. 改善程序结构
一个既长又复杂的进程可以考虑分为多个线程,成为独立或半独立的运行部分,这样有利于代码的理解和维护。
未使用多线程造成阻塞结果示例:
通过sleep来模拟耗时操作,sleep过程中,窗口无响应,5秒之后开始输出:
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include<QTimer>//定时器头文件
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void dealTimerout();//定时器槽函数
private slots:
void on_pushButton_clicked();
private:
Ui::Widget *ui;
QTimer *myTimer;//声明变量
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include<QThread>
#include<QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
myTimer = new QTimer(this);
//只要定时器启动,自动触发timeout
connect(myTimer,QTimer::timeout,this,&Widget::dealTimerout);
}
void Widget::dealTimerout()
{
static int i = 0;
i++;
//设置lcd的值
ui->lcdNumber->display(i);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
//如果定时器没有工作
if (myTimer->isActive() == false)
{
myTimer->start(100);
}
//非常复杂的数据处理,耗时较长
QThread::sleep(5);
//处理完数据后,关闭定时器
//myTimer->stop();
qDebug()<<"over";
}
执行代码,按下按钮start之后等待5秒之后输出如下所示:
二、多线程的使用
线程1: 按下start立即输出,5秒之后暂停,屏幕输出it is over
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();
};
#endif // MYTHREAD_H
mythread.cpp
#include "widget.h"
Mythread::Mythread(QObject *parent) : QThread(parent)
{
}
void Mythread::run()
{
//很复杂的数据处理
//需要耗时5秒
sleep(5);
emit isDone();
}
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include<QTimer>//定时器头文件
#include"mythread.h"//线程头文件
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void dealTimerout();//定时器槽函数
void dealDone();//线程结束槽函数
void stopThread();//停止线程槽函数
private slots:
void on_pushButton_clicked();
private:
Ui::Widget *ui;
QTimer *myTimer;//声明变量
Mythread *thread;//线程对象
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include<QThread>
#include<QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
myTimer = new QTimer(this);
//只要定时器启动,自动触发timeout
connect(myTimer,QTimer::timeout,this,&Widget::dealTimerout);
//分配空间
thread = new Mythread(this);
connect(thread,&Mythread::isDone,this,&Widget::dealDone);
//当按窗口左上角x时,窗口触发destroyed()
connect(this,&Widget::destroyed,this,&Widget::stopThread);
}
void Widget::stopThread()
{
//停止线程
thread->quit();
//等待线程处理完手头工作
thread->wait();
}
void Widget::dealDone()
{
qDebug()<<"it is over";
myTimer->stop();//关闭定时器
}
void Widget::dealTimerout()
{
static int i = 0;
i++;
//设置lcd的值
ui->lcdNumber->display(i);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
//如果定时器没有工作
if (myTimer->isActive() == false)
{
myTimer->start(100);
}
//启动线程,处理数据
thread->start();
}
输出如下所示:
线程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();
private:
bool isStop;
};
#endif // MYTHREAD_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.ui
mywidget.h
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QWidget>
#include"mythread.h"
#include<QThread>
QT_BEGIN_NAMESPACE
namespace Ui { class MyWidget; }
QT_END_NAMESPACE
class MyWidget : public QWidget
{
Q_OBJECT
public:
MyWidget(QWidget *parent = nullptr);
~MyWidget();
void dealSignal();
void dealClose();
signals:
void startThread();//启动子线程的信号
private slots:
void on_pushButton_clicked();
void on_pushButton_2_clicked();
private:
Ui::MyWidget *ui;
MyThread *myT;
QThread *thread;
};
#endif // MYWIDGET_H
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::startThread,this,&MyWidget::dealClose);
}
MyWidget::~MyWidget()
{
delete ui;
}
void MyWidget::dealClose()
{
on_pushButton_clicked();
}
void MyWidget::dealSignal()
{
static int i =0;
i++;
ui->lcdNumber->display(i);
}
void MyWidget::on_pushButton_clicked()
{
if(thread->isRunning() == true)
{
return;
}
//启动线程,但是没有启动线程处理函数
thread->start();
myT->setFlag(false);
//不能直接调用线程处理函数
//直接调用导致线程处理函数和主线程是在同一个线程
//myT->myTimeout();
//只能通过signa - slot方式调用
emit startThread();
}
void MyWidget::on_pushButton_2_clicked()
{
if(thread->isRunning() == false)
{
return;
}
myT->setFlag(true);
thread->quit();
thread->wait();
}
输出如下所示:
注意:
1. 线程函数内部不允许操作图形界面
2. connect()函数第五个参数的作用->连接方式:默认、队列、直接
默认:如果是多线程,默认使用队列;如果是单线程,默认使用直接方式
队列:槽函数所在的线程和接收者一样
直接:槽函数所在线程和发送者一样
三、使用线程绘图
mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QObject>
#include<QImage>
class MyThread : public QObject
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = nullptr);
//线程处理函数
void drawImage();
signals:
void updateImage(QImage temp);
};
#endif // MYTHREAD_H
mythread.cpp
#include "mythread.h"
#include<QPainter>
#include<QPen>
#include<QBrush>
MyThread::MyThread(QObject *parent) : QObject(parent)
{
}
void MyThread::drawImage()
{
//定义QImage绘图设备
QImage image(500,500,QImage::Format_ARGB32);
//定义画家
QPainter p(&image);
//定义画笔对象
QPen pen;
pen.setWidth(5);//设置宽度
//把画笔交给画家
p.setPen(pen);
//定义画刷
QBrush brush;
brush.setStyle(Qt::SolidPattern);//设置样式
brush.setColor(Qt::red);//设置颜色
//把画刷交给画家
p.setBrush(brush);
//定义5个点
QPoint a[] =
{
QPoint(qrand()%200,qrand()%200),
QPoint(qrand()%200,qrand()%200),
QPoint(qrand()%200,qrand()%200),
QPoint(qrand()%200,qrand()%200),
QPoint(qrand()%200,qrand()%200),
};
p.drawPolygon(a,5);
//通过信号发送图片
emit updateImage(image);
}
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include"mythread.h"
#include<QThread>
#include<QImage>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
//重写绘图事件
void paintEvent(QPaintEvent *);
void getImage(QImage);//槽函数
void dealClose();//窗口关闭槽函数
private:
Ui::Widget *ui;
QImage image;
MyThread *myT;//自定义线程对象
QThread *thread;//子线程
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include<QPainter>
#include<QThread>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//自定义类对象,分配空间,不可以指定父对象
myT = new MyThread;
//创建子线程
thread = new QThread(this);
//把自定义模块添加到子线程
myT->moveToThread(thread);
//启动子线程,但并没有启动线程处理函数
thread->start();
//线程处理函数必须通过singal-slot调用
connect(ui->pushButton,&QPushButton::pressed,myT,&MyThread::drawImage);
connect(myT,&MyThread::updateImage,this,&Widget::getImage);
connect(this,&Widget::destroyed,this,&Widget::dealClose);
}
Widget::~Widget()
{
delete ui;
}
void Widget::dealClose()
{
//退出线程
thread->quit();
//回收资源
thread->wait();
delete myT;
}
void Widget::getImage(QImage temp)
{
image = temp;
update();//更新窗口,间接调用paintEvent()
}
void Widget::paintEvent(QPaintEvent *)
{
QPainter p(this);//创建画家,指定绘图设备为窗口
p.drawImage(50,50,image);
}
输出如下所示: