当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其他线程处于挂起状态。虽然看起来所有线程都是一起执行的,但是其实每个时间只有一个线程在执行,这种方式我们成为并发。
当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行。因此,多核CPU可以同时执行多个进程。
例:给定需求计算一个复杂数据处理所花费的时间。我们有一个start按钮,和一个LCD显示时间,点击按钮开始计时并进行数据处理。
我们创建一个定时器。在start的槽函数中启动定时器,并进行数据处理,数据处理利用QThread::sleep(5)来模拟。定时器每次触发timeout信号,更新LCD数字。代码如下:
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include
#include
#include
//利用定时器计算复杂计算花费的时间
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private slots:
void on_pushButton_clicked();
private:
Ui::Widget *ui;
QTimer *_timer = new QTimer(this);
};
#endif // WIDGET_H
//widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget) {
ui->setupUi(this);
//只要定时器启动,自动触发timeout信号,每次触发timeout信号更新LCD显示
connect(_timer, &QTimer::timeout, [=]() {
static int i = 0;
i++;
ui->lcdNumber->display(i);
});
}
Widget::~Widget() {
delete ui;
}
void Widget::on_pushButton_clicked() {
//如果定时器没有工作才启动
if (_timer->isActive() == false) {
_timer->start(100);
}
//非常复杂的数据处理,需要花费5s
QThread::sleep(5);
}
//widget.ui
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ya1AvSrx-1662045421466)(C:\Users\84995\AppData\Roaming\Typora\typora-user-images\1662018717956.png)]](https://1000bd.com/contentImg/2023/10/29/173230930.png)
运行结果发现,当数据处理结束5秒后,才开始计时。这样我们就不能计算数据处理所花费的时间了。这是为什么呢?
因为这是个单线程的程序,定时器计时和数据处理不是并行处理,而是数据处理结束之后才开始定时器计时。而如果我们使用多线程,则这两个任务可以同时执行,我们就可以得到数据处理所耗费的时间了。
大多情况下,要用到多线程的主要是需要处理大量的IO操作时或处理的情况需要花费大量的时间等等。例如:
1.自定义一个线程类,继承于QThread
class MyThread : public QThread {
public:
void run(); //线程处理函数(和主线程不在同一个线程,可以同时处理)
signals:
void isDone(); //线程处理数据结束发送这个信号
}
2.主线程中启动子线程,开始并行处理。注意,主线程不能直接调用子线程的run函数,而需要通过子线程类的对象调用start()函数间接低矮用run()函数。
//主线程类中
MyThread *_thread = new MyThread(this);
_thread->start(); //通过子线程对象调用start间接调用子线程的run()函数
还是以刚才的背景为例,我们准备求得复杂信号处理的时长。我们县创建一个子线程类MyThread.
注意以下几点:
//MyThread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include
class MyThread : public QThread
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = nullptr);
signals:
void isDone();
protected:
//QThread的虚函数
//线程处理函数
//不能直接调用,通过start()间接调用
void run(); //线程处理实现入口
public slots:
};
#endif // MYTHREAD_H
//MyThread.cpp
#include "mythread.h"
MyThread::MyThread(QObject *parent) : QThread(parent) {
}
void MyThread::run() {
//复杂的数据处理,用sleep来模拟,大概耗费5s
sleep(5);
//处理结束,发送处理完成信号
emit isDone();
}
注意:
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include
#include
#include
#include
//需求,点击start按钮,显示定时器时间
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
public slots:
void DealIsDone();
private slots:
void on_pushButton_clicked();
private:
Ui::Widget *ui;
QTimer *_timer = new QTimer(this);
MyThread *_thread = new MyThread(this);
};
#endif // WIDGET_H
//widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget) {
ui->setupUi(this);
//只要定时器启动,自动触发timeout信号,每次触发timeout信号更新LCD显示
connect(_timer, &QTimer::timeout, [=]() {
static int i = 0;
i++;
ui->lcdNumber->display(i);
});
//子线程数据处理结束会发送isDone信号,这时我们暂停定时器.
connect(_thread, &MyThread::isDone, this, &Widget::DealIsDone);
}
Widget::~Widget() {
delete ui;
}
//由于线程号是有限的,线程用完之后要将其关闭
void Widget::DealIsDone() {
_timer->stop();
//停止线程
_thread->quit();
//回收子进程资源
_thread->wait();
}
void Widget::on_pushButton_clicked() {
//如果定时器没有工作才启动
if (_timer->isActive() == false) {
_timer->start(100);
}
//启动子线程同时进行数据处理
_thread->start();
}
这样,我们就可以LCD显示和数据处理同时进行,这样就可以知道数据处理的时间了。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cD4UYfUA-1662045421467)(C:\Users\84995\AppData\Roaming\Typora\typora-user-images\1662024613522.png)]](https://1000bd.com/contentImg/2023/10/29/173230847.png)
例:背景是,我们将定时器写成子线程,然后主线程就用来显示LCD的值。
1.设定一个类,继承于QObject
2.类中设置一个线程函数(只有一个线程函数)
//MyThread
class MyThread : public QObject {
public:
void myTimer() {
while (1) {
emit mySignal();
}
}
signals:
void mySignal(); //类似定时器的timeout信号
void startThread(); //启动线程信号
}
3.主线程中创建线程对象(不能指定父对象)
4.主线程中创建一个QThread子线程对象
5.把自定义线程类加入到子线程
6.启动子线程,并发射启动子线程信号
7.自定义线程函数通过与发射的子线程信号连接来实现子线程。(不能直接调用线程处理函数,直接调用线程处理函数,会导致线程处理函数和主线程在同一个线程,只能通过信号和槽的方式来调用)
//mythread.h
#define MYTHREAD_H
#include
#include
#include
class MyThread : public QObject {
Q_OBJECT
public:
explicit MyThread(QObject *parent = nullptr);
//线程处理函数
void dealTimeOut();
void setFlag(bool flag);
signals:
void myTimeOut();
public slots:
private:
bool _isStop = false;
};
#endif // MYTHREAD_H
//myThread.cpp
#include "mythread.h"
MyThread::MyThread(QObject *parent) : QObject(parent) {
}
void MyThread::dealTimeOut() {
while (!_isStop) {
//每隔1s发送一个信号
QThread::sleep(1);
emit myTimeOut();
qDebug() << "子线程号:" << QThread::currentThread() << endl;
}
}
void MyThread::setFlag(bool flag) {
_isStop = flag;
}
有以下注意事项:
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include
#include "mythread.h"
#include
#include
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
void dealSignal();
private slots:
void on_pushButtonStart_clicked();
void on_pushButtonStop_clicked();
signals:
void startThread(); //启动子线程的信号
private:
Ui::Widget *ui;
//动态分配空间不能指定父对象
MyThread *_myThread = new MyThread;
//创建子线程,指定父对象
QThread *_thread = new QThread(this);
};
#endif // WIDGET_H
//widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget) {
ui->setupUi(this);
//把自定义线程加入到子线程中
_myThread->moveToThread(_thread);
connect(this, &Widget::startThread, _myThread, &MyThread::dealTimeOut);
connect(_myThread, &MyThread::myTimeOut, this, &Widget::dealSignal);
connect(this, &Widget::destroyed, this, &Widget::on_pushButtonStop_clicked);
qDebug() << "主线程号:" << QThread::currentThread() << endl;
}
Widget::~Widget() {
delete ui;
}
void Widget::dealSignal() {
static int i = 0;
ui->lcdNumber->display(i++);
}
void Widget::on_pushButtonStart_clicked() {
//启动线程,但没有启动线程处理函数
_thread->start();
_myThread->setFlag(false);
//不能直接调用线程处理函数
//直接调用,导致线程处理函数和主线程在同一个线程
// _myThread->dealTimeOut();
//只能通过信号与槽的方式来调用
emit startThread();
}
void Widget::on_pushButtonStop_clicked() {
_myThread->setFlag(true);
_thread->quit();
_thread->wait();
}
connect()第五个参数的作用,连接方式:默认,队列,直接,第五个参数只有在多线程时才有意义。
子线程不能操作图形界面,主要用于后台运行,进行数据处理使用。
背景:子线程用于画图,主线程每次点击画图按钮,更新子线程画的图形
子线程画完图之后,发射更新图片信号,将画好的图片通过信号传送出去。
//mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include
#include
#include
#include
class MyThread : public QObject {
Q_OBJECT
public:
explicit MyThread(QObject *parent = nullptr);
//线程处理函数
void drawImage();
signals:
void updateImage(QImage img);
public slots:
};
#endif // MYTHREAD_H
//mythread.cpp
#include "mythread.h"
MyThread::MyThread(QObject *parent) : QObject(parent) {
}
//线程处理函数
void MyThread::drawImage() {
//定义QImage绘图设备
QImage image(500, 500, QImage::Format_ARGB32);
//定义画家,指定绘图设备
QPainter painter(&image);
//定义画笔对象
QPen pen;
pen.setWidth(5);
painter.setPen(pen);
//定义画刷对象
QBrush brush;
brush.setColor(Qt::blue);
brush.setStyle(Qt::Dense1Pattern);
painter.setBrush(brush);
//定义5个点
QPoint a[5] {
QPoint(qrand() % 500, qrand() % 500),
QPoint(qrand() % 500, qrand() % 500),
QPoint(qrand() % 500, qrand() % 500),
QPoint(qrand() % 500, qrand() % 500),
QPoint(qrand() % 500, qrand() % 500)
};
painter.drawPolygon(a, 5);
//通过信号发送图片
emit updateImage(image);
}
主线程调用子线程步骤如下:
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include
#include
#include
#include
#include
namespace Ui {
class Widget;
}
class Widget : public QWidget {
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
//重写绘图事件
void paintEvent(QPaintEvent *event);
public slots:
void slotGetImage(QImage img);
void slotClose(); //窗口关闭槽函数
private:
Ui::Widget *ui;
QImage _image;
MyThread *_myThread = new MyThread;
QThread *_thread = new QThread(this);
};
#endif // WIDGET_H
//widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget) {
ui->setupUi(this);
//将自定义模块添加到子线程
_myThread->moveToThread(_thread);
//启动子线程,但是并没有启动线程处理函数
_thread->start();
//线程处理函数,必须通过signal-slot调用
//drawImage槽函数和接受者_myThread在同一个子线程,而_myThread在子线程_thread中,因此就连接到了子线程。
connect(ui->pushButton, &QPushButton::clicked, _myThread, &MyThread::drawImage);
connect(_myThread, &MyThread::updateImage, this, &Widget::slotGetImage);
connect(this, &Widget::destroyed, this, &Widget::slotClose);
}
Widget::~Widget() {
delete ui;
}
void Widget::paintEvent(QPaintEvent *event) {
QPainter painter(this); //指定绘图设备为窗口
painter.drawImage(50, 50, _image);
}
void Widget::slotGetImage(QImage img) {
_image = img;
update(); //更新窗口,间接调用paintEvent
}
void Widget::slotClose() {
//结束线程
_thread->quit();
//回收线程资源
_thread->wait();
//销毁自定义线程类对象
delete _myThread;
}