Qt 多线程
我以前是有学过操作系统的,但是对于多线程依旧处于非常迷茫的阶段,虽然知道多线程是为了程序能并发运行,当时在平常的练习中很少有用到多线程,直接导致不知道如何在实际的场景中运用多线程。不过有幸看到这篇博客(Qt中多线程的使用),让我明白了多线程的运用。
正常创建的项目为单线程,一次只能处理一个事件。例如:当前程序正在执行指令A,而这时再给一个指令B,那指令B的执行需要等到指令A结束后才能执行 (程序正在跑循环,这时移动窗口是不会有响应的,但是程序结束后,窗口会弹到之前窗口移动操作结束的位置)。
为了同时可以执行多条指令,就不得不用上多线程。对于以上的情况就需要一个线程处理窗口事件,其他的线程执行逻辑运算,各司其职。
- 默认的线程在Qt中称为窗口线程,也叫主线程,负责窗口事件处理或者窗口控件数据数据更新。
- 子线程只能负责后台的业务逻辑处理,不能对窗口对象做任何的操作,如果需要对窗口对象做操作,则需要发送操作信号给主线程,让主线程进行操作。
- 子线程的开始执行需要调用 start() 函数,start() 函数默认调用 run() 函数,千万不可直接调用子线程的 run() 函数,若直接调用子线程的 run() ,这并不会启动子线程,而是将子线程中的 run() 函数让主线程执行。
- 主线程和子线程之间的通信,可采用 emit 发送信号。
1.线程类 QThread
// QThread 类常用 API
// 构造函数
QThread::QThread(QObject *parent = Q_NULLPTR);
// 判断线程中的任务是不是处理完毕了
bool QThread::isFinished() const;
// 判断子线程是不是在执行任务
bool QThread::isRunning() const;
// Qt中的线程可以设置优先级
// 得到当前线程的优先级
Priority QThread::priority() const;
void QThread::setPriority(Priority priority);
优先级:
QThread::IdlePriority --> 最低的优先级
QThread::LowestPriority
QThread::LowPriority
QThread::NormalPriority
QThread::HighPriority
QThread::HighestPriority
QThread::TimeCriticalPriority --> 最高的优先级
QThread::InheritPriority --> 子线程和其父线程的优先级相同, 默认是这个
// 退出线程, 停止底层的事件循环
// 退出线程的工作函数
void QThread::exit(int returnCode = 0);
// 调用线程退出函数之后, 线程不会马上退出因为当前任务有可能还没有完成, 调回用这个函数是
// 等待任务完成, 然后退出线程, 一般情况下会在 exit() 后边调用这个函数
bool QThread::wait(unsigned long time = ULONG_MAX);
// 和调用 exit() 效果是一样的
// 代用这个函数之后, 再调用 wait() 函数
[slot] void QThread::quit();
// 启动子线程
[slot] void QThread::start(Priority priority = InheritPriority);
// 线程退出, 可能是会马上终止线程, 一般情况下不使用这个函数
[slot] void QThread::terminate();
// 线程中执行的任务完成了, 发出该信号
// 任务函数中的处理逻辑执行完毕了
[signal] void QThread::finished();
// 开始工作之前发出这个信号, 一般不使用
[signal] void QThread::started();
// 返回一个指向管理当前执行线程的QThread的指针
[static] QThread *QThread::currentThread();
// 返回可以在系统上运行的理想线程数 == 和当前电脑的 CPU 核心数相同
[static] int QThread::idealThreadCount();
// 线程休眠函数
[static] void QThread::msleep(unsigned long msecs); // 单位: 毫秒
[static] void QThread::sleep(unsigned long secs); // 单位: 秒
[static] void QThread::usleep(unsigned long usecs); // 单位: 微秒
// 子线程要处理什么任务, 需要写到 run() 中
[virtual protected] void QThread::run();
/*
这个 run() 函数式虚函数,如果想让创建的子程序执行某个任务,需要写一个子类让其继承 QThread,并且在子类中重写父类的 run() 方法。
*/
2.线程创建
Qt 提供了 QThread 类,通过这个类就能创建子线程。Qt 一共有两种创建子线程的方式,一种是创建 QThread 的子类,让其继承 QThread,再重写父类中的 run() 方法。另一种就是创建一个 QObject 的子类,然后在主线程中再创建一个子线程,最后将 QObeject 的子类对象移动到子线程对象中去。
2.1.1 方法一
-
创建 QThread 的子类,继承 QThread.
class MyThread:public QThread{ ...... }
-
重写父类中的 run() 方法,在函数体内编写子线程要处理的业务流程
class MyThread:public QThread{ protected: void run(){ ...... } }
-
在主线程中创建子线程对象
MyThread *sub = new MyThread;
-
启动子线程
sun->start();
2.1.1 示例
在同一个窗口中同时执行两条指令,假设只有一个线程,让其记数,会发现窗口不能更新数字,如果此时用鼠标托动窗口,会出现无反应的情况。
在下面的窗口中,点击按键1,会在按键1的上方出现数字,点击按键2,会在按键2的上方出现数字。
//mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
class mythread : public QThread
{
Q_OBJECT
public:
explicit mythread(QObject *parent = 0);
void run();
signals:
void Num(int);
};
#endif // MYTHREAD_H
//mythread.cpp
#include "mythread.h"
#include <QDebug>
mythread::mythread(QObject *parent):
QThread(parent)
{
}
void mythread::run(){
int num = 0;
qDebug() << "Thread 开始执行" << QThread::currentThread();
while(num <= 100) {
emit Num(num ++);
QThread::sleep(1);
}
qDebug() << "Thread 结束执行";
}
// widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private slots:
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
// widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include "mythread.h"
#include <QDebug>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
qDebug() << "主线程 Thread: " << QThread::currentThread();
mythread *my = new mythread;
connect(my,&mythread::Num,this,[=](int num){
ui->label->setText(QString("%1").arg(num));
});
connect(ui->time_btn_1,&QPushButton::clicked,this,[=]{
my->start();
});
mythread *my2 = new mythread; //此处如果采用mythread my2的方式定义会报错。
connect(my2,&mythread::Num,this,[=](int num){
ui->label_2->setText(QString("%1").arg(num));
});
connect(ui->time_btn_2,&QPushButton::clicked,this,[=]{
my2->start();
});
}
Widget::~Widget()
{
delete ui;
}
这种方式创建子线程是非常简单的,但是假设一个子线程需要处理多个任务,那所有的处理逻辑都需要写到 run() 函数中,这样会使得处理逻辑混乱,不利于维护。
2.2.1方法二
-
创建 QObject 的子类
class MyWork:public QObject{ ...... }
-
在子类中添加一个公共的成员函数,函数体内就是需要执行的业务逻辑
class MyWork:public QObject{ public: void working(){ ...... } }
-
在主线程中创建一个 QThread 对象
QThread *sub = new QThread;
-
在主线程中创建工作类的对象(不能为创建的对象指定父类)
MyWork *work = new MyWork(this); //错误,此处指明父类后,直接加入到了主线程中,此处的错误可能会因为编译器版本的不同而出现不同的错误。 MyWork *work = new MyWork;
-
将 MyWork 对象移动到创建的子线程对象中,调用 QObject 类提供的 moveToThread() 方法
work->moveToThread(sub);
2.2.2 示例
需求如上
// mywork.h
#ifndef MYWORK_H
#define MYWORK_H
#include <QObject>
class MyWork : public QObject
{
Q_OBJECT
public:
explicit MyWork(QObject *parent = nullptr);
void working();
signals:
void Num(int);
public slots:
};
#endif // MYWORK_H
// mywork.cpp
#include "mywork.h"
#include <QThread>
#include <QDebug>
MyWork::MyWork(QObject *parent) : QObject(parent)
{
}
void MyWork::working(){
qDebug() << "子线程开始执行:" << QThread::currentThread();
int num = 0;
while(num < 10) {
emit Num(num ++);
QThread::msleep(1000);
}
qDebug() << "子线程结束!";
}
// widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private slots:
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
// widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QThread>
#include "mywork.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
qDebug() << "主线程 Thread: " << QThread::currentThread();
QThread *sonThread = new QThread;
QThread *sonThread2 = new QThread;
MyWork *work = new MyWork;//不可使用全局指针,否则子线程无法启动
MyWork *work2 = new MyWork;
work->moveToThread(sonThread);
work2->moveToThread(sonThread2);
sonThread->start();
sonThread2->start();
connect(work,&MyWork::Num,this,[=](int num){
ui->label->setNum(num);
});
connect(work2,&MyWork::Num,this,[=](int num){
ui->label_2->setNum(num);
});
connect(ui->time_btn_1,&QPushButton::clicked,work,&MyWork::working);
connect(ui->time_btn_2,&QPushButton::clicked,work2,&MyWork::working);
}
Widget::~Widget()
{
delete ui;
}
这种方式便于处理多个不相关的业务流程,可以创建多个类似于 MyWork 的类,将业务流程放在类的公共成员函数中,然后将这个业务类的实例对象移动到对应的子线程中 moveToThread() 就可以了,这样可以让编写的程序更加灵活,可读性更强,更易于维护。
参考博客:Qt中多线程的使用