最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

【QT】一文学会 QT 多线程(QThread )

网站源码admin4浏览0评论

【QT】一文学会 QT 多线程(QThread )

一、Qt 多线程概述

在 Qt 中,多线程的处理一般是通过 QThread类 来实现。

  • QThread 代表一个在应用程序中可以 独立控制 的线程,也可以和进程中的其他线程共享数据
  • QThread 对象管理程序中的一个控制线程。

创建线程的两种方式

① 使用 QThread 类  QThread 类是 Qt 中实现多线程的基础类之一,通过继承 QThread 类并重写其 run() 函数可以实现自定义线程逻辑。 线程类

代码语言:javascript代码运行次数:0运行复制
#ifndef WORKER_H
#define WORKER_H
 
#include <QThread>
 
class Worker : public QThread
{
public:
    Worker();
 
    void run();
 
    void printFunc();
 
 
};
 
#endif // WORKER_H

实现如下:

代码语言:javascript代码运行次数:0运行复制
#include "Worker.h"
#include <QDebug>
 
Worker::Worker()
{
 
}
 
void Worker::run()
{
    qDebug()<<"子线程ThreadID: "<<QThread::currentThreadId();
}
 
void Worker::printFunc()
{
    qDebug()<<"子线程成员函数ThreadID: "<<QThread::currentThreadId();
}

main 函数

代码语言:javascript代码运行次数:0运行复制
#include <iostream>
#include <QDebug>
#include "Worker.h"
 
using namespace std;
 
int main()
{
    Worker w;
 
    w.start();
 
    qDebug()<<"主线程ThreadID: "<<QThread::currentThreadId();
 
    w.printFunc();
 
    while (1)
    {
 
    }
 
    return 0;
}

结果分析:

  • 主线程和子线程执行的顺序不确定,偶尔主线程在前,偶尔子线程在前。
  • 子线程类的成员函数包括槽函数是运行在主线程当中的,只有run()函数运行在子线程中。
  • 如果在run()函数中调用子线程类成员函数,那么该成员函数运行在子线程中。

② 使用 moveToThread() moveToThread() 是 Qt 中用于将对象移动到另一个线程的方法。通过调用 moveToThread() 函数,可以将一个 QObject 对象从当前线程移动到另一个线程中,从而实现对象在新线程中执行特定的任务。

代码语言:javascript代码运行次数:0运行复制
    在多线程编程中,通常会使用 moveToThread() 方法来将耗时的任务或需要在单独线程中执行的逻辑移动到单独的线程中,以避免阻塞主线程(通常是 GUI 线程)的执行。

线程类:

代码语言:javascript代码运行次数:0运行复制
#ifndef WORKER_H
#define WORKER_H
 
#include <QObject>
 
class Worker : public QObject
{
    Q_OBJECT
public:
    Worker();
 
    void printFunc();
 
public slots:
    void doWork();
 
    void doWork2();
 
    void doWork3();
 
signals:
 
    void testdoWork3();
 
};
 
#endif // WORKER_H

实现如下:

代码语言:javascript代码运行次数:0运行复制
#include "Worker.h"
#include <QDebug>
#include <QThread>
 
Worker::Worker()
{
 
}
 
void Worker::printFunc()
{
    qDebug() << "成员函数ThreadID:"<<QThread::currentThreadId();
 
}
 
void Worker::doWork()
{
    qDebug() << "doWork ThreadID:"<<QThread::currentThreadId();
}
 
void Worker::doWork2()
{
    qDebug() << "doWork2 ThreadID:"<<QThread::currentThreadId();
}
 
void Worker::doWork3()
{
    qDebug() << "doWork3 ThreadID:"<<QThread::currentThreadId();
}

main 函数

代码语言:javascript代码运行次数:0运行复制
#include "mainwindow.h"
 
#include <QApplication>
#include "Worker.h"
#include <QDebug>
#include <QThread>
 
int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    //MainWindow w;
    //w.show();
 
    Worker worker;
    QThread thread;
 
    worker.moveToThread(&thread);
 
    QObject::connect(&thread, &QThread::started, &worker, &Worker::doWork);         //第一槽函数
    QObject::connect(&thread, &QThread::started, &worker, &Worker::doWork2);        //第二槽函数
    QObject::connect(&worker, &Worker::testdoWork3, &worker, &Worker::doWork3);     //第三槽函数
    
    //启动线程
    thread.start();
 
    //调用成员数
    worker.printFunc();
 
    //发送自定义信号
    emit worker.testdoWork3();
 
    while (1) {
 
    }
 
    return a.exec();
}

结果分析:

  • 槽函数无论是线程的信号触发还是自定义信号触发,槽函数都在新线程里运行。
  • 成员函数和主函数运行在主线程当中。

对比 QThread 和 moveToThread()

① QThread 方式:

使用场景:

  • 当需要创建一个独立的线程来执行某个任务,且需要对线程的整个生命周期进行管理时,适合使用 QThread 方式。
  • 当任务逻辑相对简单或独立,不需要频繁地进行线程间通信时,可以选择使用 QThread 方式。

优点:

  • 可以直接控制线程的生命周期,包括启动、停止、等待线程退出等。
  • 适合单一任务的线程处理,结构相对清晰易懂。
  • 相对直观,可以比较容易理解和使用。

缺点:

  • 需要手动管理线程之间的通信和数据共享,容易引入线程安全问题。
  • 繁琐的线程管理和同步机制可能增加代码复杂度和风险。

② moveToThread() 方式:

使用场景:

  • 当需要将一个 QObject 对象移动到指定的线程中执行任务,或者需要多个对象在同一线程中协同工作时,适合使用 moveToThread() 方式。
  • 当需要灵活地控制对象和线程之间的关系,进行复杂的线程间通信时,可以选择使用 moveToThread() 方式。

优点:

  • 可以利用信号和槽机制方便地实现对象在不同线程中的通信。
  • 可以更灵活地管理对象和线程的关系,避免直接操作线程带来的问题。
  • 适合处理复杂的多线程通信和任务分发。

缺点:

  • 无法直接控制线程的启动和停止,线程的生命周期由对象决定,可能使得线程管理稍显复杂。
  • 对对象的线程移动可能引入一些额外的开销,需要谨慎设计线程之间的交互逻辑。

总结: 选择使用 QThread 或 moveToThread() 方式创建线程取决于具体需求和情况。可以根据以下原则进行选择:

  1. 如果需要独立管理整个线程的生命周期、简单的多线程操作,并且不涉及复杂的线程间通信,可以选择 QThread 方式。
  2. 如果需要灵活地管理对象与线程之间的关系、复杂的多线程通信和任务分发,可以选择 moveToThread() 方式。

综上所述,根据项目需求、任务复杂度和开发方便性来选择适合的创建线程方式

二、QThread 常用 API

方法名

作用

run()

线程入口函数

start()

通过调用 run() 开始执行线程,操作系统会根据 优先级参数调度 线程。如果线程正在运行,则这个函数什么都不会做

currentThread()

返回一个指向管理当前执行线程的 QThread 的指针

isRunning()

如果线程正在运行返回 true,否则反之

sleep() / msleep() / usleep()

使线程休眠,单位为 秒/毫秒/微秒

wait()

阻塞线程,直到满足以下任何一个条件: 与此 QThread 对象关联的线程已经完成执行(即当它从run()返回时)。如果线程已经完成,这个函数将返回 true。如果线程尚未启动,它也返回 true。 已经过了几毫秒。如果时间是 ULONG MAX(默认值),那么等待永远不会超时(线程必须从run()返回)。如果等待超时,此函数将返回false。这提供了与 POSIX pthread_join()函数类似的功能。

terminate()

终止线程执行。(可以选择立即终止,也可以不立即终止)取决于操作系统的 调度策略,在 terminate() 之后使用 QThread::wait() 来确保

finished()

当线程结束时会发出该信号,通过其来实现线程清理工作

在使用QThread类中的常用函数时,有一些注意事项需要注意:

  • start() 函数:调用start()函数启动线程时,会自动调用线程对象的run()方法。不要直接调用run()方法来启动线程,应该使用start()函数。
  • wait() 函数:wait()函数会阻塞当前线程,直到线程执行完成。在调用wait()函数时需要确保不会发生死锁的情况,避免主线程和子线程相互等待对方执行完成而无法继续。
  • terminate() 函数:调用terminate()函数会强制终止线程,这样可能会导致资源未能正确释放,造成内存泄漏等问题。因此应该尽量避免使用terminate()函数,而是通过设置标志让线程自行退出。
  • quit() 函数:quit()函数用于终止线程的事件循环,通常与exec()函数一起使用。在需要结束线程事件循环时,可以调用quit()函数。
  • finished 信号:当线程执行完成时会发出finished信号,可以连接这个信号来处理线程执行完成后的操作。
  • yieldCurrentThread() 函数:yieldCurrentThread()函数用于让当前线程让出时间片,让其他线程有机会执行。使用时应该注意避免过多的调用,否则会影响程序性能。

三、线程使用 – 倒计时

【实现倒计时页面】

1、创建项目,以 Widget 作为基类,设计 UI 界面如下:

image-20250131195525774

2、新建一个类,并且继承于 QThread 类

image-20250131195801401

3、thread.h 声明实现如下:

image-20250131200711715

run 方法 在 thread.cpp 实现如下:

代码语言:javascript代码运行次数:0运行复制
void Thread::run()
{
    // 这里我们不能直接去修改界面内容
    // 原因:存在线程安全问题,Qt 针对界面的控件状态的任何修改必须在 主函数 中进行

    // 这里我们就仅仅针对时间进行计时即可
    // 每隔一秒 通知主线程更新界面内容
    for(int i = 0; i < 10; i++){
        sleep(1);
        // 发送一个信号 通知主线程
        emit notify();
    }
}

4、Widget 声明实现如下:

image-20250131201008609

Widget.cpp 代码如下:

代码语言:javascript代码运行次数:0运行复制
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 连接信号槽,通过槽函数更新界面
    connect(&thread, &Thread::notify, this, &Widget::handle);

    // 启动线程
    thread.start();
}

void Widget::handle()
{
    // 此处修改界面内容
    int val = ui->lcdNumber->intValue();
    val--;
    ui->lcdNumber->display(val);
}
  • 演示这里就省略了,大家可以自行演示结果

发布评论

评论列表(0)

  1. 暂无评论