【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(); //释放信号量 
//在另⼀个线程中进⾏类似操作 
相关推荐
Quz13 小时前
QML 基础按钮:Button、RoundButton 与 DelayButton
qt
Quz13 小时前
QML ToolTip 组件:图标、多行文本与阴影
qt
xcyxiner14 小时前
DicomViewer14(读取图像按固定窗宽窗位显示)
qt
马里马里奥-1 天前
从零搭建AI Agent工具链的技术文章大纲
开发语言·qt
Quz2 天前
QML 与 JavaScript 交互方式:内联函数、外部文件、信号槽与工作线程
javascript·qt
Quz2 天前
QML ToolTip 组件:延迟提示与自定义外观
qt
wWYy.2 天前
RPC详解
qt·rpc·php
Fu_Lin_2 天前
Qt嵌入式从零基础到精通(01):Qt嵌入式开发全景图:从桌面Qt到ARM Linux
linux·arm开发·qt·qt5·嵌入式linux
海清河晏1112 天前
Qt实战:从零构建美化登录界面
开发语言·c++·qt
Fu_Lin_2 天前
《Qt嵌入式从零基础到精通》前言与阅读指南
开发语言·qt