问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Qt多线程编程指南:提升应用性能与用户体验

创作时间:
作者:
@小白创作中心

Qt多线程编程指南:提升应用性能与用户体验

引用
CSDN
1.
https://blog.csdn.net/Colorful___/article/details/139253311

在现代软件开发中,多线程编程已成为一个不可或缺的技能,尤其是在需要处理复杂任务和提高应用程序性能的场合。Qt,作为一个跨平台的应用程序框架,提供了强大的多线程支持,使得开发者能够充分利用多核处理器的优势,开发出响应迅速且高效的应用程序。本文将深入探讨Qt多线程的基本概念、API使用、线程安全问题以及同步机制,旨在帮助开发者更好地理解和运用Qt的多线程功能。

Qt 多线程概述

Qt 多线程 和 Linux 中线程,本质是一个东西。Linux 中的各种和线程相关的 原理 和 注意事项,都是在Qt中适用的。Qt 中的多线程 API 是基于 Linux 系统提供的 pthread 库进行封装的,相比原生 API 更加易用。C++11 引入的 std::thread 也提供了更好的线程支持,而 Qt 的多线程 API 在设计上参考了 Java 的线程库 API,更加适合客户端开发。

要创建线程,需要创建 QThread 的实例,并重写其中的 run 函数来指定线程的入口函数。虽然有些场景对性能要求极高,但 Qt 主要面向客户端开发,更注重开发效率和用户体验。

QThread 常用 API

  • start():调用系统 API 创建线程,并自动执行 run 函数。
  • wait():让一个线程等待另一个线程执行结束。

使用线程

通过线程可以实现类似定时器的功能。例如,创建一个新线程进行计时,每秒更新一次界面。但需要注意的是,Qt 中所有涉及界面控件状态的修改都必须在主线程中执行,以避免线程安全问题。

以下是具体的代码示例:

// widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "thread.h"
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    Thread thread;
    void handle();
private:
    Ui::Widget *ui;
};
#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);
    // 连接信号槽,通过槽函数跟新界面
    connect(&thread, &Thread::notify, this, &Widget::handle);
    // 要启动一下线程
    thread.start();
}
Widget::~Widget()
{
    delete ui;
}
void Widget::handle()
{
    // 此处修改界面内容
    int value = ui->lcdNumber->intValue();
    value--;
    ui->lcdNumber->display(value);
}
// thread.h
#ifndef THREAD_H
#define THREAD_H
#include <QWidget>
#include <QThread>
class Thread : public QThread
{
    Q_OBJECT
public:
    Thread();
    // 要用的目的是重写父类的方法 run 方法
    void run();
signals:
    void notify(); // 只用声明不用定义
};
#endif // THREAD_H
// thread.cpp
#include "thread.h"
Thread::Thread()
{
}
void Thread::run()
{
    // 在这个 run 中。能否直接去进行修改界面内容呢?
    // 不可以!!!
    // 虽然不可以修改界面,但是可以针对时间来进行计时
    // 当每到一秒钟的时候,通过信号槽,来通知主线程,负责更新界面内容
    for (int i = 0; i < 10; ++i) {
        // sleep 本身是 QThead 的成员函数, 就可以直接调用
        sleep(1);
        // 发送一个信号,通知主线程
        emit notify();
    }
}


多线程的使用场景

在客户端开发中,多线程主要用于提升用户体验。通过多线程执行耗时操作,可以避免主线程被阻塞,确保用户界面的响应性。例如,在文件上传下载、网络通信等场景中,使用单独的线程处理密集的 I/O 操作,可以防止整个程序无响应。

线程安全问题

加锁

为了解决线程安全问题,可以使用锁来保护公共资源。Qt 提供了 QMutex 类来实现互斥锁。为了避免忘记解锁导致死锁,Qt 还提供了 QMutexLocker 类,利用 RAII 机制自动管理锁的生命周期。

// 创建锁对象
static QMutex mutex;

// 使用锁
for (int i = 0; i < 50000; ++i) {
    QMutexLocker locker(&mutex);
    ++num;
}

读写锁

Qt 还提供了 QReadWriteLockQReadLockerQWriteLocker 类来实现读写锁。允许多个线程同时读取共享资源,但只允许一个线程写入。

QReadWriteLock rwLock;
//在读操作中使⽤读锁
{
 QReadLocker locker(&rwLock); //在作⽤域内⾃动上读锁
 
 //读取共享资源
 //...
 
} //在作⽤域结束时⾃动解读锁
//在写操作中使⽤写锁
{
 QWriteLocker locker(&rwLock); //在作⽤域内⾃动上写锁
 
 //修改共享资源
 //...
 
} //在作⽤域结束时⾃动解写锁

条件变量与信号量

条件变量

Qt 提供了 QWaitCondition 类来实现条件变量。通过 waitwakeAll 方法可以控制线程的等待和唤醒。

QMutex mutex;
QWaitCondition condition;
//在等待线程中
mutex.lock();
//检查条件是否满⾜,若不满⾜则等待
while (!conditionFullfilled()) // 
{
 condition.wait(&mutex); //等待条件满⾜并释放锁
}
//条件满⾜后继续执⾏
//...
mutex.unlock();
//在改变条件的线程中
mutex.lock();
//改变条件
changeCondition();
condition.wakeAll(); //唤醒等待的线程
mutex.unlock();

信号量

Qt 提供了 QSemaphore 类来实现信号量,用于控制同时访问共享资源的线程数量。

QSemaphore semaphore(2); //同时允许两个线程访问共享资源
//在需要访问共享资源的线程中
semaphore.acquire(); //尝试获取信号量,若已满则阻塞
//访问共享资源
//...
semaphore.release(); //释放信号量
//在另⼀个线程中进⾏类似操作

总结

本文详细介绍了Qt多线程的各个方面,从基础概念到实际应用,再到线程安全和同步机制的讨论。首先,我们概述了Qt多线程与Linux线程的关系,并比较了Qt、C++11和Linux原生API的优缺点。接着,我们深入探讨了QThread的常用API和如何使用线程来执行耗时操作,同时强调了Qt中界面更新必须在主线程中进行的原则。

在多线程的使用场景中,我们讨论了多线程在客户端开发中的重要性,尤其是在提升用户体验方面的作用。随后,文章重点讨论了线程安全问题,包括加锁机制、读写锁以及条件变量和信号量的使用,这些都是确保多线程程序正确运行的关键技术。

通过本文的学习,开发者应该能够更加自信地在Qt中实现多线程编程,编写出既高效又稳定的应用程序。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号