QT的线程
文章目录
1、为什么要用线程?
看如下的dome:
ui的界面如下:
mywidget.h
#include <QWidget>
#include <QTimer>
QT_BEGIN_NAMESPACE
namespace Ui { class MyWidget; }
QT_END_NAMESPACE
class MyWidget : public QWidget
{
Q_OBJECT
public:
MyWidget(QWidget *parent = nullptr);
~MyWidget();
QTimer *ptime;
private slots:
void on_pushButton_clicked();
void TimeDealSlot(void);
private:
Ui::MyWidget *ui;
};
mywidget.cpp
#include "mywidget.h"
#include "ui_mywidget.h"
#include <QThread>
MyWidget::MyWidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::MyWidget)
{
ui->setupUi(this);
ptime = new QTimer(this);
connect(ptime, &QTimer::timeout, this, &MyWidget::TimeDealSlot);
}
MyWidget::~MyWidget()
{
delete ui;
}
void MyWidget::TimeDealSlot(void)
{
static int i = 0;
i++;
ui->lcdNumber->display(i);
}
void MyWidget::on_pushButton_clicked()
{
/* 启动定时器 */
ptime->start(1000);
/* 这个6s的延时当做是很复杂的数据处理 */
QThread::sleep(6);
}
按下开始按钮启动定时器然后lcdNumber每隔一秒加1显示。但是后面有6s的延时的操作。其结果并不是我们想着这样。
真实的结果是6s执行完后lcdNumber每隔一秒才加1显示。因为在6s的延时时。都在处理这个延时。其它事情没法处理。
只能一件一件做完。当然如果每件事情都不是很复杂那也不会有影响。但这个复杂的6s延时是会有影响的。所以对于很复杂的
数据处理就需要另外开启一个线程。让这个线程来处理。
线程的优势:
多线程程序有以下几个特点:
2、QT线程
1. QT4线程
- 定义一个类继承与QThread。
- 在类里面重写void run()的虚函数(这个run就是线程处理函数并且run的访问限定符是protected。
- start()启动线程函数run。注意不能直接调用run()函数。
- 线程的退出Thread->quit() pThread->wait();
模拟的流程如下:
mywidget.h
#include <QWidget>
#include <QTimer>
#include "mythread.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MyWidget; }
QT_END_NAMESPACE
class MyWidget : public QWidget
{
Q_OBJECT
public:
MyWidget(QWidget *parent = nullptr);
~MyWidget();
QTimer *ptime;
Mythread *pThread;
private slots:
void on_pushButton_clicked();
void TimeDealSlot(void);
void WidgetSlot(void);
private:
Ui::MyWidget *ui;
};
mywidget.cpp
#include "mywidget.h"
#include "ui_mywidget.h"
#include <QThread>
MyWidget::MyWidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::MyWidget)
{
ui->setupUi(this);
ptime = new QTimer(this);
pThread = new Mythread(this);
/* 定时器溢出信号timeout */
connect(ptime, &QTimer::timeout, this, &MyWidget::TimeDealSlot);
/* 当按窗口右上角x时,窗口触发destroyed()信号 */
connect(this, &MyWidget::destroyed, this, &MyWidget::WidgetSlot);
}
MyWidget::~MyWidget()
{
delete ui;
}
void MyWidget::TimeDealSlot(void)
{
static int i = 0;
i++;
ui->lcdNumber->display(i);
}
void MyWidget::WidgetSlot(void)
{
/* 停止线程 */
pThread->quit();
/* 等待线程处理完手头动作 */
pThread->wait();
}
void MyWidget::on_pushButton_clicked()
{
/* 如果定时器没有工作 */
if(ptime->isActive() == false) {
/* 启动定时器 */
ptime->start(1000);
}
/* 启动线程 不能直接调run函数 */
pThread->start();
}
mythread.h
#include <QThread>
class Mythread : public QThread
{
Q_OBJECT
public:
explicit Mythread(QObject *parent = nullptr);
protected:
//QThread的虚函数
//线程处理函数
//不能直接调用,通过start()间接调用
void run();
signals:
};
mythread.cpp
#include "mythread.h"
Mythread::Mythread(QObject *parent)
: QThread{parent}
{
}
void Mythread::run()
{
/* 这个6s的延时当做是很复杂的数据处理 */
QThread::sleep(6);
}
当开启一个线程并且把这个延时放到线程里处理。按下开始按钮启动定时器然后lcdNumber每隔一秒加1显示。这样就不会有影响。
1. QT5线程
ui的界面
- 创建一个类继承与QObject
- 在类中定义一个线程处理函数(有且只有一个)
#include <QObject>
class mythread : public QObject
{
Q_OBJECT
public:
explicit mythread(QObject *parent = nullptr);
/* 自定义线程处理函数 */
void ThreadDeal(void);
};
- 在主线程里定义自定义线程与 子线程。并且自定义线程不能指定父对象。指定了会报一个错误。
QThread *pThread; /* 子线程 */
mythread *pMyThread; /* 自定义线程 */
//动态分配空间,不能指定父对象
pMyThread = new mythread;
//创建子线程
pThread = new QThread(this);
- 把自定义线程加入到子线程。
//把自定义线程加入到子线程中 子线程 与 自定义线程建立联系
pMyThread->moveToThread(pThread);
- 启动子线程,但是没有启动线程处理函数
//启动子线程,但是没有启动线程处理函数
pThread->start();
- 在启动子线程后发送一个自定义信号。通过信号与槽去启动线程处理函数。直接调用启动是没有用的。
//只能通过 signal - slot 方式调用
emit startThread(); /* 发送信号 */
/* 接收信号去启动线程处理函数 */
connect(this, &MyWidget::startThread, pMyThread, &mythread::myTimeout);
- 线程退出
pThread->quit();
pThread->wait();
总体的流程:
mywidget.h
#include <QWidget>
#include <QThread>
#include "mythread.h"
#include <QTimer>
QT_BEGIN_NAMESPACE
namespace Ui { class MyWidget; }
QT_END_NAMESPACE
class MyWidget : public QWidget
{
Q_OBJECT
public:
MyWidget(QWidget *parent = nullptr);
~MyWidget();
QThread *pThread; /* 子线程 */
mythread *pMyThread; /* 自定义线程 */
QTimer *ptime;
private slots:
void on_pushButtonStart_clicked();
void on_pushButtonStop_clicked();
void WidgetSlot(void);
void TimeDealSlot(void);
signals:
void StartThreadDealSignal();
private:
Ui::MyWidget *ui;
};
mywidget.cpp
#include "mywidget.h"
#include "ui_mywidget.h"
MyWidget::MyWidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::MyWidget)
{
ui->setupUi(this);
ptime = new QTimer(this);
/* 自定义线程 不能指定父对象 */
pMyThread = new mythread;
/* 创建子线程 */
pThread = new QThread(this);
/* 把自定义线程加入到子线程 */
pMyThread->moveToThread(pThread);
/* 定时器溢出信号timeout */
connect(ptime, &QTimer::timeout, this, &MyWidget::TimeDealSlot);
/* 当按窗口右上角x时,窗口触发destroyed()信号 */
connect(this, &MyWidget::destroyed, this, &MyWidget::WidgetSlot);
/* 信号与槽去调用线程处理函数 */
connect(this, &MyWidget::StartThreadDealSignal, pMyThread, &mythread::ThreadDeal);
}
MyWidget::~MyWidget()
{
delete ui;
}
void MyWidget::TimeDealSlot(void)
{
static int i = 0;
i++;
ui->lcdNumber->display(i);
}
void MyWidget::on_pushButtonStart_clicked()
{
/* 线程启动已经开启就退出 */
if(pThread->isRunning() == true) {
return;
}
qDebug() << "主线程号:" << QThread::currentThread();
/* 如果定时器没有工作 */
if(ptime->isActive() == false) {
/* 启动定时器 */
ptime->start(1000);
}
/* 启动子线程 但并没有启动线程处理函数 */
pThread->start();
/* 发送StartThreadDealSignal信号启动线程处理函数 */
emit StartThreadDealSignal();
}
void MyWidget::WidgetSlot(void)
{
on_pushButtonStop_clicked();
delete pMyThread;
}
void MyWidget::on_pushButtonStop_clicked()
{
/* 线程启动已经停止就退出 */
if(pThread->isRunning() == false) {
return;
}
/* 停止线程 */
pThread->quit();
/* 等待线程处理完手头动作 */
pThread->wait();
}
mythread.h
#include <QObject>
class mythread : public QObject
{
Q_OBJECT
public:
explicit mythread(QObject *parent = nullptr);
/* 自定义线程处理函数 */
void ThreadDeal(void);
signals:
};
mythread.cpp
#include "mythread.h"
#include<QThread>
#include<QDebug>
mythread::mythread(QObject *parent)
: QObject{parent}
{
}
void mythread::ThreadDeal(void)
{
/* 这个6s的延时当做是很复杂的数据处理 */
QThread::sleep(6);
qDebug() << "子线程号:" << QThread::currentThread();
}
3. QT5线程的退出
pThread->quit()是会等到线程手头的工作处理完才退出。但如果线程的工作处理不完。比如死循环。那怎么退出呢?
/* 这个6s的延时当做是很复杂的数据处理 */
while(1) {
QThread::sleep(6);
qDebug() << "子线程号:" << QThread::currentThread();
}
- 强制退出
用pThread->terminate()代替pThread->quit。但是这样可能会导致内存问题。强制退出风险很大。 - 在自定义类里定义一个标志位
mythread.h
void setThreadQuitFlag(int flag); /* 设置标志位函数 */
int ThreadQuitFlag; // 定义一个标志位
mythread.cpp
#include "mythread.h"
#include<QThread>
#include<QDebug>
mythread::mythread(QObject *parent)
: QObject{parent}
{
ThreadQuitFlag = false;
}
void mythread::ThreadDeal(void)
{
/* 这个6s的延时当做是很复杂的数据处理 */
while(1) {
QThread::sleep(6);
qDebug() << "子线程号:" << QThread::currentThread();
if (ThreadQuitFlag == true) {
break; // 退出线程
}
}
}
void mythread::setThreadQuitFlag(int flag)
{
ThreadQuitFlag = flag;
}
在主线程里一开始启动线程设置标志为
pMyThread->setThreadQuitFlag(false);
在主线程里结束线程设置标志为true就可以退出线程
pMyThread->setThreadQuitFlag(true);
4. connect的第五个参数
只介绍队列与直接方式。多线程时才有意义。
如果是多线程,默认使用队列方式。
如果是单线程, 默认使用直接方式。
队列: 槽函数所在的线程和接收者一样。
直接:槽函数所在线程和发送者一样。
1. 直接方式
在如上QT5线程的dome中设置第五个参数 Qt::DirectConnection
/* 信号与槽去调用线程处理函数 */
connect(this, &MyWidget::StartThreadDealSignal, pMyThread, &mythread::ThreadDeal, Qt::DirectConnection);
槽函数所在线程和发送者(主窗口)一样。
2. 队列方式
在如上QT5线程的dome中设置第五个参数 Qt::DirectConnection
/* 信号与槽去调用线程处理函数 */
connect(this, &MyWidget::StartThreadDealSignal, pMyThread, &mythread::ThreadDeal, Qt::QueuedConnection);
队列: 槽函数所在的线程和接收者(子线程)一样。
3、QT线程绘制QImage图片然后给到主线程显示刷新。
具体的流程:
1.点击绘图按钮调用线程处理函数
2. 线程里绘图图片但是不显示。绘制一次刷新一次。发送图片给主线程
3. 主线程拿到子线程的图片。在主窗口刷新显示。
伪代码如下:
注意:QImage才能在线程里绘图。
ui界面如下:就一个按钮
widget.h
#include <QWidget>
#include "mythread.h"
#include <QThread>
#include <QImage>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
QTimer *pTime;
//重写绘图事件
void paintEvent(QPaintEvent *);
public slots:
void getImage(QImage); //槽函数
void dealClose(); //窗口关闭槽函数
private:
Ui::Widget *ui;
QImage image;
MyThread *myT; //自定义线程对象
QThread *thread; //子线程
};
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);
this->resize(600,600);
//自定义类对象,分配空间,不可以指定父对象
myT = new MyThread;
//创建子线程
thread = new QThread(this);
//把自定义模块添加到子线程
myT->moveToThread(thread);
//启动子线程,但是,并没有启动线程处理函数
thread->start();
//线程处理函数,必须通过signal - 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);
}
mythread.h
#include <QObject>
#include <QImage>
class MyThread : public QObject
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = 0);
//线程处理函数
void drawImage();
signals:
void updateImage(QImage temp);
public slots:
};
mythread.cpp
#include "mythread.h"
#include <QPainter>
#include <QPen>
#include <QBrush>
#include <QImage>
#include <QRandomGenerator>
MyThread::MyThread(QObject *parent) : QObject(parent)
{
}
void MyThread::drawImage()
{
//定义QImage绘图设备
QImage image(500, 500, QImage::Format_ARGB32); // QImage::Format_ARGB32透明的颜色
//定义画家,指定绘图设备
QPainter p(&image);
//定义画笔对象
QPen pen;
pen.setWidth(5); //设置宽度
//把画笔交给画家
p.setPen(pen);
//定义画刷
QBrush brush;
brush.setStyle(Qt::SolidPattern); //设置样式
brush.setColor(QColor(QRandomGenerator::global()->bounded(0,255),QRandomGenerator::global()->bounded(0,255),
QRandomGenerator::global()->bounded(0,255))); //随机设置颜色
//把画刷交给画家
p.setBrush(brush);
//定义点
QPoint a[] =
{
QPoint(QRandomGenerator::global()->bounded(0,500), QRandomGenerator::global()->bounded(0,500)),
QPoint(QRandomGenerator::global()->bounded(0,500), QRandomGenerator::global()->bounded(0,500)),
QPoint(QRandomGenerator::global()->bounded(0,500), QRandomGenerator::global()->bounded(0,500)),
QPoint(QRandomGenerator::global()->bounded(0,500), QRandomGenerator::global()->bounded(0,500)),
QPoint(QRandomGenerator::global()->bounded(0,500), QRandomGenerator::global()->bounded(0,500)),
QPoint(QRandomGenerator::global()->bounded(0,500), QRandomGenerator::global()->bounded(0,500))
};
/* 绘制多边形 */
p.drawPolygon(a, sizeof(a)/sizeof(a[0]));
//通过信号发送图片
emit updateImage(image);
}
4、多线程使用过程中注意事项
需要移动到子线程中处理的模块类,创建的对象的时候不能指定父对象。但在关闭窗口时要记得释放。
//自定义类对象,分配空间,不可以指定父对象
myT = new MyThread;
线程不能操作UI对象(从Qwidget直接或间接派生的窗口对象)
#include "mythread.h"
#include<QThread>
#include<QDebug>
#include<QMessageBox>
mythread::mythread(QObject *parent)
: QObject{parent}
{
ThreadQuitFlag = false;
}
void mythread::ThreadDeal(void)
{
/* 这个6s的延时当做是很复杂的数据处理 */
while(1) {
QThread::sleep(6);
qDebug() << "子线程号:" << QThread::currentThread();
if (ThreadQuitFlag == true) {
break; // 退出线程
}
/* 在线程处理函数里操作UI对象 */
QMessageBox::about(NULL,"关于","在线程处理函数内部不能操作图形界面");
}
}
QMessageBox::about(NULL,“关于”,“在线程处理函数内部不能操作图形界面”)
这样会导致程序崩掉。
在线程处理函数内部不能操作图形界面。只能是纯数据处理。
5.总结
主要介绍QT的线程的两种用法。connect的第五个参数 。以及线程退pThread->terminate()与pThread->quit函数不一样。以及用pThread->quit。如果线程退不出来(死循环)用标志位来退出。还有线程里不能操作图形界面。