【Qt教程13】Qt多线程

原创作者:郑同学的笔记
原创地址:https://zhengjunxue.blog.csdn.net/article/details/137329188
qq技术交流群:921273910

一、线程类 QThread

Qt中提供了一个线程类,通过这个类就可以创建子线程了,Qt中一共提供了两种创建子线程的方式,后边会依次介绍其使用方式。

1、QThread 类常用 API

先来看一下这个类中提供的一些常用API函数:

QThread::QThread(QObject *parent = Q_NULLPTR);——构造函数
bool QThread::isFinished() const;——判断线程中的任务是不是处理完毕了
bool QThread::isRunning() const;——判断子线程是不是在执行任务

Priority QThread::priority() const;——得到当前线程的优先级
void QThread::setPriority(Priority priority);——Qt中的线程可以设置优先级

优先级:
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);

1.2 信号槽

// 和调用 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();

1.3 静态函数

// 返回一个指向管理当前执行线程的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);	// 单位: 微秒

1.4 任务处理函数

// 子线程要处理什么任务, 需要写到 run() 中
[virtual protected] void QThread::run();

这个run()是一个虚函数,如果想让创建的子线程执行某个任务,需要写一个子类让其继承QThread,并且在子类中重写父类的run()方法,函数体就是对应的任务处理流程。另外,这个函数是一个受保护的成员函数,不能够在类的外部调用,如果想要让线程执行这个函数中的业务流程,需要通过当前线程对象调用槽函数start()启动子线程,当子线程被启动,这个run()函数也就在线程内部被调用了

二、demo

要求:举一个简单的数数的例子,点击开始按钮,一直数数,间隔1s数一次

1、假如只有一个线程,让其一直数数

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_startBtn_clicked()
{
    updateCount();
}

void MainWindow::updateCount()
{
    for(int i=0;i<2000;++i)
    {
        ++m_counter;
        ui->label->setText(QString::number(m_counter));
        //QThread::sleep(1);//也是不可以的
    }
}

点击一次,显示2000,再点击一次,显示4000
在这里插入图片描述
在这里插入图片描述

2、使用定时器——显示正常,鼠标拖动页面,出现页面不跟鼠标的情况

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_startBtn_clicked()
{
    if(m_timerId == 0)
    {
        this->m_timerId = startTimer(100);
    }
}

void MainWindow::updateCount()
{
    // 更新计数并在窗口中显示
        ++m_counter;
        ui->label->setText(QString::number(m_counter));
        QThread::sleep(1);
}

void MainWindow::timerEvent(QTimerEvent *event)
{
    // 定时器事件触发时更新计数
    if (event->timerId() == m_timerId)
        updateCount();
}

在这里插入图片描述

3、使用子线程——方式一(显示正常,可以正常拖动)

-mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "mythread.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

protected:
//    void timerEvent(QTimerEvent *event) override; // 声明timerEvent函数

private slots:
    void on_startBtn_clicked();
private slots:
    void updateCount(int m_counter);
//    void updateCount();

private:
    Ui::MainWindow *ui;
    int m_counter=0;
    int m_timerId=0;
    MyThread* subThread;
};

#endif // MAINWINDOW_H

-mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"


MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    subThread = new MyThread;

    connect(subThread, &MyThread::curNumber, this, &MainWindow::updateCount);

}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_startBtn_clicked()
{
    // 启动子线程
    subThread->start();
}

void MainWindow::updateCount(int m_counter)
{
    // 更新计数并在窗口中显示
        ui->label->setText(QString::number(m_counter));

}
  • mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>

class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);

protected:
    void run();

signals:
    // 自定义信号, 传递数据
    void curNumber(int num);

public slots:
};

#endif // MYTHREAD_H

  • mythread.cpp
#include "mythread.h"
#include <QDebug>

MyThread::MyThread(QObject *parent) : QThread(parent)
{

}

void MyThread::run()
{
    qDebug() << "当前线程对象的地址: " << QThread::currentThread();

    int num = 0;
    while(1)
    {
        emit curNumber(num++);
        if(num == 10000000)
        {
            break;
        }
        QThread::sleep(1);
    }
    qDebug() << "run() 执行完毕, 子线程退出...";
}

三、QThreadPool

Qt中的 QThreadPool 类管理了一组 QThreads, 里边还维护了一个任务队列。QThreadPool 管理和回收各个 QThread 对象,以帮助减少使用线程的程序中的线程创建成本。每个Qt应用程序都有一个全局 QThreadPool 对象,可以通过调用 globalInstance() 来访问它。也可以单独创建一个 QThreadPool 对象使用。

线程池常用的API函数如下:

// 获取和设置线程中的最大线程个数
int maxThreadCount() const;
void setMaxThreadCount(int maxThreadCount);

// 给线程池添加任务, 任务是一个 QRunnable 类型的对象
// 如果线程池中没有空闲的线程了, 任务会放到任务队列中, 等待线程处理
void QThreadPool::start(QRunnable * runnable, int priority = 0);
// 如果线程池中没有空闲的线程了, 直接返回值, 任务添加失败, 任务不会添加到任务队列中
bool QThreadPool::tryStart(QRunnable * runnable);

// 线程池中被激活的线程的个数(正在工作的线程个数)
int QThreadPool::activeThreadCount() const;

// 尝试性的将某一个任务从线程池的任务队列中删除, 如果任务已经开始执行就无法删除了
bool QThreadPool::tryTake(QRunnable *runnable);
// 将线程池中的任务队列里边没有开始处理的所有任务删除, 如果已经开始处理了就无法通过该函数删除了
void QThreadPool::clear();

// 在每个Qt应用程序中都有一个全局的线程池对象, 通过这个函数直接访问这个对象
static QThreadPool * QThreadPool::globalInstance();

一般情况下,我们不需要在Qt程序中创建线程池对象,直接使用Qt为每个应用程序提供的线程池全局对象即可。得到线程池对象之后,调用start()方法就可以将一个任务添加到线程池中,这个任务就可以被线程池内部的线程池处理掉了,使用线程池比自己创建线程的这种多种多线程方式更加简单和易于维护。

具体的使用方式如下:

mywork.h

class MyWork :public QRunnable
{
    Q_OBJECT
public:
    explicit MyWork();
    ~MyWork();

    void run() override;
}

mywork.cpp


二、注意点

1、 多线程这个类继承QObject

2、在这个类中添加一个公共的成员函数,函数体就是我们要子线程中执行的业务逻辑

在主线程中创建一个QThread对象, 这就是子线程的对象

C++
1
QThread* sub = new QThread;
在主线程中创建工作的类对象(千万不要指定给创建的对象指定父对象)

C++
1
2
MyWork* work = new MyWork(this); // error
MyWork* work = new MyWork; // ok
将MyWork对象移动到创建的子线程对象中, 需要调用QObject类提供的moveToThread()方法

C++
1
2
3
4
// void QObject::moveToThread(QThread *targetThread);
// 如果给work指定了父对象, 这个函数调用就失败了
// 提示: QObject::moveToThread: Cannot move objects with a parent
work->moveToThread(sub); // 移动到子线程中工作
启动子线程,调用 start(), 这时候线程启动了, 但是移动到线程中的对象并没有工作

调用MyWork类对象的工作函数,让这个函数开始执行,这时候是在移动到的那个子线程中运行的

作者: 苏丙榅
链接: https://subingwen.cn/qt/thread/
来源: 爱编程的大丙
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

郑同学的笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值