【Qt】系统相关(3)——多线程

【Qt】系统相关(3)------多线程

  • [1. Qt多线程概念](#1. Qt多线程概念)
  • [2. 线程安全](#2. 线程安全)
    • [2.1 互斥锁](#2.1 互斥锁)
    • [2.2 条件变量](#2.2 条件变量)
    • [2.3 信号量](#2.3 信号量)

1. Qt多线程概念

首先说明一下,Qt中的多线程和linux中的多线程是一个东西,只不过Qt中对其进行了封装,在Qt中可以更方便的使用。在Qt中,多线程的处理一般是通过QThread类来实现。QThread代表一个在应用程序中可以独立控制的线程,也可以和进程中的其他线程共享数据。QThread对象管理程序中的一个控制线程。

在Qt中想要创建线程,首先就要创建出y8geQThread的实例,并且在创建线程的时候重点要制定线程的入口函数,而这个入口函数是通过创建一个QThread的子类,然后重写其中的run函数,来起到指定入口函数的方式(这里就用到了多态,就有点类似于我们之前学的事件)。

QThtread常用API

API 作用
run() 线程的入口函数
start() 通过调用ru0开始执行线程。操作系统将根据优先级参数调度线程。如果线程已经在运行,这个函数什么也不做。
currentThread() 返回一个指向管理当前执行线程的QThread的指针。
isRunning() 如果线程正在运行则返回true;否则返回false。
sleep()/msleep()/usleep() 使线程休眠,单位为秒/毫秒/微秒
wait() 阻塞线程,直到满足以下任何一个条件:与此QThread对象关联的线程已经完成执行(即当它从run0返回时)。如果线程已经完成,这个函数将返回true。如果线程尚未启动,它也返回true。已经过了几毫秒。如果时间是ULONG_MAX(默认值),那么等待永远不会超时(线程必须从run0返回)。如果等待超时,此函数将返回false。这提供了与POSIX pthread_join0函数类似的功能。终止线程的执行。线程可以立即终止,也可以不立即终止,这取决于操作系统的调
terminate() 度策略。在terminate0之后使用QThread:wait0来确保。
finished() 当线程结束时会发出该信号,可以通过该信号来实现线程的清理工作。

示例:通过创建一个线程来实现一个定时器,在之前的事件章节中是通过QTimer和QTimerEvetn来实现的定时器,而本章节同样可以使用线程的方式来实现。具体步骤就是,创建一个线程类继承自QThread,然后重写run函数,在run函数中实现一个for循环,并且每隔一秒中就发射一个信号,而信号由主线程接收,也就相当于实现了每隔一秒钟的定时器。

  1. 创建一个继承自QThread的类

thread.h头文件

c 复制代码
#include <QWidget>
#include <QThread>
class Thread : public QThread
{
    Q_OBJECT
public:
    Thread();
    // 重写run
    void run();
signals:
    // 自定义信号
    void notify();
};

thread.cpp源文件

c 复制代码
void Thread::run()
{
    // 在run中,也就是在线程中不能直接修改界面,而是要主线程才能修改界面
    for (int i = 0; i < 10; ++i){
        // sleep本身是QThread的成员函数,可以直接调用
        sleep(1);
        // 发出一个信号
        emit notify();
    }
}

widget.h头文件

c 复制代码
class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
public slots:
    void handel();

private:
    Ui::Widget *ui;
    Thread thread;
};

widget.cpp源文件

c 复制代码
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 关联线程
    connect(&thread, &Thread::notify, this, &Widget::handel);
    // 启动线程
    thread.start();
}

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

void Widget::handel()
{
    int value = ui->lcdNumber->intValue();
    --value;
    ui->lcdNumber->display(value);
}

2. 线程安全

实现线程互斥和同步常用的类有:

  1. 互斥锁:QMutex、QMutexLocker
  2. 条件变量:QWaitCondition
  3. 信号量:QSemaphore
  4. 读写锁:QReadLocker、QWriteLocker、QReadWriteLock

2.1 互斥锁

互斥锁是一种保护和防止多个线程同时访问同一对象实例的方法,在Qt中,互斥锁主要是通过QMutex类来处理。

QMutex

特点:QMutex是Qt框架提供的互斥锁类,用于保护共享资源的访问,实现线程间的互斥操作。

用途:在多线程环境下,通过互斥锁来控制对共享数据的访问,确保线程安全。

**示例:**创建两个线程,对临界资源num进行加法操作

thread.h

c 复制代码
#include <QWidget>
#include <QThread>
#include <QMutex>
class Thread : public QThread
{
    Q_OBJECT
public:
    Thread();
    // 重写run
    void run();
    // 声明两个静态成员
    static int num;
    static QMutex mutex;
};

thread.cpp

c 复制代码
#include "thread.h"
// 定义
int Thread::num = 0;
QMutex Thread::mutex;
Thread::Thread()
{

}

void Thread::run()
{
    for (int i = 0; i < 100; ++i){
        mutex.lock();
        num++;
        mutex.unlock();
    }
}

widget.cpp

c 复制代码
#include <QDebug>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 创建两个线程
    Thread t1;
    Thread t2;
    // 启动线程
    t1.start();
    t2.start();
    // 线程等待,让主线程等待这两个线程结束,这里需要注意的是,如果没有线程等待的话,启动程序是由三个线程的,一个是主线程,两个创建 
    // 的线程,而当创建的线程启动的时候,主线程也会接着往下走,那么当执行的qDebug的时候可能两个线程还没有结束,所以打印出的结果
    // 就会出错。
    t1.wait();
    t2.wait();

    qDebug() << Thread::num;
}

QMutexLocker

特点:QMutexLocker是QMutex的辅助类,使用RAl(Resource Acquisition Is Initialization)方式对互斥锁进行上锁和解锁操作。

用途:简化对互斥锁的上锁和解锁操作,避免忘记解锁导致的死锁等问题。

于是上面的代码就可以写成

thread.cpp

c 复制代码
#include "thread.h"
// 定义
int Thread::num = 0;
QMutex Thread::mutex;
Thread::Thread()
{

}

void Thread::run()
{
    for (int i = 0; i < 100; ++i){
        QMutexLocker locker(&mutex);
        num++;
    }
}

QReadWriteLocker、QReadLocker、.QWriteLocker

特点:

  • QReadWriteLock是读写锁类,用于控制读和写的并发访问。
  • QReadLocker用于读操作上锁,允许多个线程同时读取共享资源。
  • QWriteLocker用于写操作上锁,只允许一个线程写入共享资源。

用途:在某些情况下,多个线程可以同时读取共享数据,但只有一个线程能够进行写操作。读写锁提

供了更高效的并发访问方式。

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

2.2 条件变量

在多线程编程中,假设除了等待操作系统正在执行的线程之外,某个线程还必须等待某些条件满足才能执行,这时就会出现问题。这种情况下,线程会很自然地使用锁的机制来阻塞其他线程,因为这只是线程的轮流使用,并且该线程等待某些特定条件,人们会认为需要等待条件的线程,在释放互斥锁或读写锁之后进入了睡眠状态,这样其他线程就可以继续运行。当条件满足时,等待条件的线程将被另一个线程唤醒。在Qt中,专门提供了QWaitCondition类来解决像上述这样的问题。

特点 :QWaitCondition是Qt框架提供的条件变量类,用于线程之间的消息通信和同步。

用途:在某个条件满足时等待或唤醒线程,用于线程的同步和协调。

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

2.3 信号量

有时在多线程编程中,需要确保多个线程可以相应的访问一个数量有限的相同资源。例如,运行程序的设备可能是非常有限的内存,因此我们更希望需要大量内存的线程将这一事实考虑在内,并根据可用的内存数量进行相关操作,多线程编程中类似问题通常用信号量来处理。信号量类似于增强的互斥锁,不仅能完成上锁和解锁操作,而且可以跟踪可用资源的数量。

特点 :QSemaphore是Qt框架提供的计数信号量类,用于控制同时访问共享资源的线程数量。

用途:限制并发线程数量,用于解决一些资源有限的问题。

c 复制代码
QSemaphore semaphore(2); //同时允许两个线程访问共享资源 
//在需要访问共享资源的线程中 
semaphore.acquire(); //尝试获取信号量,若已满则阻塞 
//访问共享资源 
//...
semaphore.release(); //释放信号量 
//在另⼀个线程中进⾏类似操作 
相关推荐
初阳7854 小时前
【Qt】系统相关(2)——文件
开发语言·qt
尘中远18 小时前
【Qwt 7.0 系列】坐标轴与刻度系统 —— 刻度引擎、网格、图例与刻度朝内
qt·数据可视化·qcustomplot·qwt·工业软件·科学绘图
sycmancia20 小时前
Qt——多线程间的互斥
开发语言·qt
尘中远1 天前
【Qwt 7.0 系列】常用图表类型实战 —— 柱状图、散点图、箱线图与直方图
qt·qwt·工业软件·科学绘图
尘中远1 天前
【Qwt 7.0 系列】交互功能详解 —— 平移、缩放、坐标轴交互与数据拾取
qt·数据可视化·绘图·qcustomplot·qwt·科学绘图
sycmancia1 天前
Qt——进程与线程的概念
qt
郝学胜-神的一滴1 天前
Qt 高级编程 034:深耕QWidget底层内核—彻底吃透无边框窗口设计核心原理
开发语言·c++·qt·程序人生·软件开发·用户界面
尘中远1 天前
【Qwt 7.0 系列】3D 数据可视化 —— OpenGL 高性能三维绘图
qt·3d·qcustomplot·qwt·科学绘图·高性能绘图
满天星83035771 天前
【Qt】控件(二) (geometry及与frameGeometry的区别)
开发语言·qt