概述:
在多核时代,CPU 的主频已经进入瓶 颈,另辟蹊径地提高程序运行效率就是使用线程,充分利用多核的优势。线程可以看做是“轻量级进程
”,线程即可以由操作系统管理,也可以由应用程序管
1.为什么要使用线程:
我们都知道,进程线程的概念是非常重要的,也可以看看Linux下的线程,一般是在多任务的时候需要用到线程进程,说简单一点处理多核可以跑多个while(1)
之外,进程线程也可以跑多个while(1)
我们使用一个定时器和LCD显示定时器的计数.
比如以下示例:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
/*定时器的创建*/
MyTimer = new QTimer(this);
/*定时器启动自动触发timeout信号*/
connect(MyTimer, &QTimer::timeout, this, &Widget::dealtimer);
}
Widget::~Widget()
{
delete ui;
}
void Widget::dealtimer()
{
static int i = 0;
i++;
/*设置LCD*/
ui->lcdNumber->display(i);
}
/*当界面有过于复杂的数据处理的时候需要用到多线程
避免界面卡死*/
void Widget::on_Lcd_Start_clicked()
{
/*如果定时器没有工作*/
if(MyTimer->isActive() == false)
{
MyTimer->start(1000);
}
/*非常复杂的数据处理 耗时较长*/
QThread::sleep(5);
/*处理完数据关闭定时器*/
//MyTimer->stop();
}
注:使用QThread
中的sleep
函数,让程序等待5s,我们现在目前只有一个主线程,所以在点击按钮之后会造成定时器虽然设置了,但是LCD的显示数字是不会该变得,因为sleep
了5s所以说需要等5s之后才会开始变化,也就是说如果sleep
换成一个数据处理的函数时候,在数据处理函数执行的这段时间,其余的程序无法运行,会造成窗口卡住,无响应等问题。所以我们使用多线程来解决执行复杂数据处理函数时不去影响都别的程序运行。
2.线程的创建使用方法一:
2.1线程创建的步骤:
- 自定义一个类,继承于
QThread
,并且只有一个线程处理函数(和主线程不再同一个线程),这个线程处理函数就是重写父类中的run函数。 - 线程处理函数里面写入需要执行的复杂数据处理
- 启动线程不能直接调用
run
函数,需要使用对象来调用start
函数实现线程启动 - 线程处理函数执行结束后可以定义一个信号来告诉主线程
- 最后关闭线程
2.2将上述程序进行修改
/*******************mythread.h**************************/
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
/*自定义一个类重写线程处理函数*/
class Mythread : public QThread
{
Q_OBJECT
public:
explicit Mythread(QObject *parent = nullptr);
/*线程处理函数,不能直接调用,通过start简介调用*/
protected:
/*线程处理函数*/
void run();
signals:
/*自定义一个线程处理函数指向完成后的一个信号*/
void runDone();
};
#endif // MYTHREAD_H
/*******************mythread.cpp**************************/
#include "mythread.h"
Mythread::Mythread(QObject *parent) : QThread(parent)
{
}
void Mythread::run()
{
/*很复杂的数据处理*/
sleep(5);
/*执行结束后发送信号*/
emit runDone();
}
/*******************widget.cpp****************************/
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
/*定时器的创建*/
MyTimer = new QTimer(this);
/*定义自定义线程类对象*/
thread = new Mythread(this);
/*定时器启动自动触发timeout信号*/
connect(MyTimer, &QTimer::timeout, this, &Widget::dealtimer);
/*connect自定义线程类中的信号 可以知道线程处理函数执行完成*/
connect(thread, &Mythread::runDone, this, &Widget::dealthread);
/*关闭窗口时触发以下信号 关闭窗口后关闭线程*/
connect(this, &Widget::destroyed, this, &Widget::stopthread);
}
Widget::~Widget()
{
delete ui;
}
void Widget::dealtimer()
{
static int i = 0;
i++;
/*设置LCD*/
ui->lcdNumber->display(i);
}
/*当界面有过于复杂的数据处理的时候需要用到多线程
避免界面卡死*/
void Widget::on_Lcd_Start_clicked()
{
/*如果定时器没有工作*/
if(MyTimer->isActive() == false)
{
MyTimer->start(100);
}
/*非常复杂的数据处理 耗时较长*/
//QThread::sleep(5);
thread->start();
/*处理完数据关闭定时器*/
//MyTimer->stop();
}
/*线程处理函数指向完成后发送的信号处理*/
void Widget::dealthread()
{
/*关闭定时器*/
MyTimer->stop();
qDebug() << "处理完毕";
}
void Widget::stopthread()
{
thread->quit();
/*等待线程处理完事情之后*/
thread->wait();
}
执行效果如下:
至于为上面是在45的时候停止的,由于中间过程启动时间导致的。
对于关闭线程时候使用的两种方法:
void terminate()
/*此函数直接关闭线程不等待线程任务结束*/
void quit()
/*此函数等待线程任务结束之后关闭线程*/
3.线程的创建使用方法二:
3.1线程创建的步骤:
- 自定义一个类,只需要继承
QObject
即可,并且线程处理函数名字随便取,但是也只有一个线程处理函数 - 创建一个自定义线程类的对象,不能指定父对象
- 创建一个
QThread
类的对象,可以指定父对象 - 将自定义线程对象加入到
QThread
类的对象,使用 - 启动线程的时候要注意:启动
QThread
类的对象线程,调用start函数只是启动了线程,但是没有开启线程处理函数,线程处理函数的开启需要用到信号槽机制。 - 关闭线程
3.2示例程序
/******************mythread.h*************************************/
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QObject>
#include <QThread>
#include <QDebug>
class mythread : public QObject
{
Q_OBJECT
public:
explicit mythread(QObject *parent = nullptr);
void thread();
void setflag(bool flag = true);
signals:
void mysignal();
private:
bool isStop;
};
#endif // MYTHREAD_H
/***************************mythread.cpp*********************************************/
#include "mythread.h"
mythread::mythread(QObject *parent) : QObject(parent)
{
isStop = false;
}
void mythread::thread()
{
while (!isStop)
{
QThread::sleep(1);
emit mysignal();
qDebug() << "子线程号:" << QThread::currentThread();
if(isStop) break;
}
}
void mythread::setflag(bool flag)
{
isStop = flag;
}
/******************************mywidgt.h************************************************/
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QWidget>
#include <mythread.h>
#include <QDebug>
QT_BEGIN_NAMESPACE
namespace Ui { class MyWidget; }
QT_END_NAMESPACE
class MyWidget : public QWidget
{
Q_OBJECT
public:
MyWidget(QWidget *parent = nullptr);
~MyWidget();
mythread *testthread;
QThread *thread;
signals:
/*启动子线程的信号*/
void startsignal();
private slots:
void on_startPushbutton_clicked();
void delsignals();
void on_closePushbutton_clicked();
void dealclose();
private:
Ui::MyWidget *ui;
};
#endif // MYWIDGET_H
/************************************mywidgt.cpp*********************************************/
#include "mywidget.h"
#include "ui_mywidget.h"
#include <QThread>
MyWidget::MyWidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::MyWidget)
{
ui->setupUi(this);
/*动态分配空间不能知道父对象*/
testthread = new mythread();
/*创建子线程*/
thread = new QThread(this);
/*把自定义线程加入到子线程中*/
testthread->moveToThread(thread);
connect(testthread, &mythread::mysignal, this, &MyWidget::delsignals);
qDebug() << "主线程号:" << QThread::currentThread();
connect(this, &MyWidget::startsignal, testthread, &mythread::thread);
connect(this, &MyWidget::destroyed, this, &MyWidget::dealclose);
/*线程函数内部不允许操作图形界面 一般用数据处理
connect第五个参数作用:
只有在多线程的时候采意义 连接方式 直接 队列
默认的时候如果是多线程则使用队列
单线程使用直接方式
队列和直接差别:
队列:槽函数所在的线程和接受者一样
直接:槽函数所在线程和发送者一样
*/
}
MyWidget::~MyWidget()
{
delete ui;
}
void MyWidget::on_startPushbutton_clicked()
{
if(thread->isRunning() == true)
{
return;
}
/*启动线程但是没有启动线程处理函数*/
thread->start();
/*不能直接调用线程处理函数 直接调用导致线程处理函数和主线程处于同一线程*/
emit startsignal();
}
void MyWidget::delsignals()
{
static int i = 0;
i ++;
ui->lcdNumber->display(i);
}
void MyWidget::on_closePushbutton_clicked()
{
if(thread->isRunning() == false)
{
return ;
}
testthread->setflag();
thread->quit();
thread->wait();
}
void MyWidget::dealclose()
{
/* 释放对象*/
delete testthread;
on_closePushbutton_clicked();
}
执行效果如下:
上述程序当中线程处理函数变成了一个死循环,要想使用quit
方式关闭线程,需要注意是无法关闭的因为是一个死循环,所以说quit
处于一直等待线程任务结束的状态,但是线程任务是一个死循环,所以无法结束,我们采用一个标志位的方式,需要关闭线程的时候让死循环break
退出循环,再关闭线程