QT--线程

一、线程QThread

  • QThread 类提供不依赖平台的管理线程的方法,如果要设计多线程程序,一般是从 QThread继承定义一个线程类,在自定义线程类里进行任务处理。
  • qt拥有一个GUI线程,该线程阻塞式监控窗体,来自任何用户的操作都会被gui捕获到,并处理;如果有耗时的任务,不推荐在GUI中处理.怎么办?? 创建线程,交给线程去耗时!

1.QThread类简要说明

  1. 一个QThread类的对象管理一个线程。该线程包括
  • 执行函数体,这是线程执行的主要代码部分。
  • 函数体中有一个死循环,用于保持线程的持续运行,直到条件满足。
  • 线程私有空间,每个线程都有自己的独立数据和堆栈空间。
  1. QThread 提供了完整的线程功能,并且内置了一个虚函数 run() 用于处理线程任务。我们可以通过重写 run() 方法来定义线程的具体行为。编写线程的具体方法为继承QThread并重写run()函数。最好是直接定义一个线程类(实际上也这样做)。
  2. GUI线程和控件访问:只有主GUI线程可以访问和操作窗体上的控件。如果其他线程尝试直接访问这些控件,会导致程序崩溃。为了在线程中更新UI,可以使用信号和槽机制。
cpp 复制代码
class MyThread : public QThread {
    Q_OBJECT
signals:
    void updateUI(int value);

public:
    void run() override {
        for (int i = 0; i < 10; ++i) {
            emit updateUI(i); // 发出信号更新UI
            QThread::sleep(1);
        }
    }
};

// 在主窗口类中连接信号和槽
connect(ptMyThread, &MyThread::updateUI, this, &MainWindow::updateUIFunction);
  1. 线程的启动和停止使用start()和stop()函数即可。这也是可以捕获的信号,可以用来连接槽函数,不过要加上ed。
  2. 出现了此类错误error: undefined reference to `vtable for myThread,那么就将该错误发生的头文件和函数体文件移除该工程,然后再添加进来。
  3. 代码举例


2.线程间通信

1.通过共享资源的方式可以进行线程间通信。

  1. 结构体通信
    1. 值得注意的就是使用之前要加锁,使用之后要解锁
c 复制代码
QMutex buflock;//其实就是互斥信号量。
buflock.lock();
buflock.unlock();
    1. 结构体要在使用该结构体的线程中声明,定义在外面,方便其他线程使用或声明,当然互斥锁也要有哈。
    1. 一般通过构造函数传入或写出数据。
    1. 在重写run()函数里有while循环,或者是死循环,具体情况而定
    1. 当然也要有休眠函数,给其他线程一点时间执行嘛。
    1. 代码举例
cpp 复制代码
//rethread.h
#ifndef RETHREAD_H
#define RETHREAD_H
#include<QThread>
#include<QMutex>

struct msg_struct{
    int temp;
    int shidu;
    char des[128];
};

class thread_write:public QThread{
public:
    thread_write();
    thread_write(struct msg_struct *pmsg,QMutex *pMutex);//写
    ~thread_write();
    void run() override;//写。重写
private:
    struct msg_struct *pShareMsg;
    QMutex *pMutex;
};

class thread_read:public QThread{
public:
    thread_read(struct msg_struct *pmsg,QMutex *pMutex);//写
    thread_read();
    ~thread_read();

    void run() override;//重写
private:
    struct msg_struct *pShareMsg;
    QMutex *pMutex;

};

#endif // RETHREAD_H

//rethread.cpp
#include "rethread.h"
#include<QDebug>



thread_write::thread_write()
{

}

thread_write::thread_write(msg_struct *pmsg, QMutex *pMutex)
{

    pShareMsg = pmsg;
    this->pMutex=pMutex;
}

thread_write::~thread_write()
{

}

void thread_write::run()
{
    char ch = 'A';
    while(1){
        pMutex->lock();
        for(int i= 0;i<127;i++){
            pShareMsg->des[i]=ch;
            if(i%20==0){
                QThread::sleep(1);
            }
        }
        pMutex->unlock();
        QThread::msleep(10);
        ch++;
    }
}

thread_read::thread_read(msg_struct *pmsg, QMutex *pMutex)
{
    pShareMsg = pmsg;
    this->pMutex = pMutex;

}

thread_read::thread_read()
{

}

thread_read::~thread_read()
{

}

void thread_read::run()
{
    QThread::sleep(1);
    while(1){
        pMutex->lock();
        qDebug()<<"thread read"<<__func__<<" "<<pShareMsg->des;
        pMutex->unlock();
        QThread::sleep(5);
    }
}

//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include"rethread.h"
#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

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

private:
    Ui::Widget *ui;
    struct msg_struct *pShareMsg;
    QMutex *pMutex;
    thread_read *pThreadRead;
    thread_write *pThreadWrite;
};
#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);
    pShareMsg = new struct msg_struct;
    pMutex = new QMutex;
    pThreadRead  = new thread_read(pShareMsg,pMutex);
    pThreadWrite = new thread_write(pShareMsg,pMutex);

    pThreadWrite->start();
    pThreadRead->start();
}

Widget::~Widget()
{
    delete ui;
}
  1. 信号和槽
  • 在一个线程中定义一个信号,然后将其连接到另一个线程中的槽函数,通过信号的触发来调用槽函数。这是Qt中最常用的线程间通信方法。
  • 这之中有一个线程铁定是GUI线程。才可以使用信号和槽
  • 例如

    输出为
  • 值得注意的是不要在GUI程序中加入sleep睡眠之类的代码,容易造成程序崩溃。

3.线程间同步

  • 线程同步是指在多线程环境中,协调线程之间的执行顺序和数据共享,确保线程以预期的方式访问共享资源。同步的主要目的是避免竞争条件(race conditions)和数据不一致性。
1.基于互斥量的线程同步QMutex
  1. 互斥量可以保证在任意时刻只有一个线程可以访问共享资源,从而避免竞争条件和数据不一致性。
  2. 定义一把互斥锁
cpp 复制代码
//widget.h
QMutex *pMutex;
//widget.cpp
pMutex = new QMutex;
  1. 上锁,如果互斥量已经被其他线程锁定,当前线程将被阻塞,直到互斥量可用。
cpp 复制代码
pMutex.lock();
  1. 解锁,访问共享资源后,线程需要释放互斥量的锁,以便其他线程可以访问该资源。
cpp 复制代码
pMutex.unlock();
  1. 尝试上锁,函数tryLock()尝试锁定一个互斥量,如果成功锁定就返回true,如果其他线程已经锁定了这个互斥量就返回false,不等待。有参数则等待。
cpp 复制代码
pMutex.try_Lock();//括号内可以有参数,以毫秒为单位,表示最多等待多少毫秒
2. 基于读写锁的线程同步QReadWriteLock
  • 基于读写锁(Read-Write Lock)的线程同步是一种高效的同步机制,适用于读多写少的场景。与互斥锁不同,读写锁允许多个 线程同时读取共享资源,但在写入资源时,只允许一个线程进行写操作,这样可以提高程序的并发性能。
  1. 定义一把读写锁
cpp 复制代码
//widget.h
QReadWriteLock *pRWLock;
//Widget.cpp
pRWLock = new QReadWriteLock;
  1. pRWLock.lockForRead(); //以只读方式锁定资源,如果有其他线程以写入方式锁定资源,这个函数会被阻塞
  2. pRWLock.lockForWrite();//以写入方式锁定资源,如果其他线程以读或写方式锁定资源,这个函数会被阻塞
  3. pRWLock.unlock();//解锁
  4. 它们可以在前面加上try,如tryLockForRead();表示尝试以读的方式锁上资源,括号内的参数可以表示尝试多少毫秒
3. 基于条件等待的线程同步QWaitCondition
  • QWaitCondition 提供了一种改进的线程同步方法,QWaitCondition 通过与 QMutex QReadWriteLock 结合使用,可以使一个线程在满足一定条件时通知其他多个线程,使其他多个线程及时进行响应,这样比只使用互斥量或读写锁效率要高一些。
  • 定义
cpp 复制代码
QMutex mutex;
QReadWriteLock readWriteLock;
QWaitCondition condition;
  1. bool wait(QMutex *lockedMutex, unsigned long time = ULONG_MAX),释放lockMutex这个互斥信号量。线程进入休眠,等待被唤醒,默认无限等待。若被唤醒则返回true;若超时则返回false。
  2. bool wait(QReadWriteLock *lockedReadWriteLock, unsigned long time = ULONG_MAX),释放lockedReadWriteLock这个读写锁,并让线程进入等待状态直到被唤醒或者超时。默认情况下,无限期等待。若被唤醒则返回true;若超时则返回false。
  3. void wakeAll():唤醒所有处于等待状态的线程。唤醒顺序不确定,由操作系统的调度策略决定。
  4. void wakeOne():唤醒一个处于等待状态的线程。具体唤醒哪个线程不确定,由操作系统的调度策略决定。
4. 基于信号量的线程同步
  • 信号量(QSemaphore)是用于控制多个线程对共享资源的访问的同步原语。它是一种计数器,允许你在特定时间允许多个线程同时访问某个资源。信号量可以用来限制访问的线程数。
  1. 使用方法
方法名 描述 参数 返回值
构造函数
QSemaphore(int initialCount = 1, int maxCount = 1) 创建一个信号量,初始计数和最大计数。 initialCount:初始计数值 maxCount:最大计数值
基本方法
acquire(int num = 1) 获取 num 个资源,若资源不足,线程阻塞。 num:需要获取的资源数量
release(int num = 1) 释放 num 个资源,增加信号量的计数器。 num:需要释放的资源数量
带超时的方法
acquire(int num, unsigned long timeout = ULONG_MAX) 获取 num 个资源,直到超时。 num:需要获取的资源数量 timeout:超时时间(毫秒) bool:成功获取资源返回 true,超时返回 false
检查方法
tryAcquire(int num = 1) 尝试获取 num 个资源,若资源不足则立即返回。 num:需要获取的资源数量 bool:成功获取资源返回 true,否则返回 false
available() const 返回可用资源的数量。 int:可用资源数量
其他方法
setCount(int count) 设置信号量的计数器值。 count:新的计数值
maximumCount() const 返回信号量的最大值。 int:最大值
currentCount() const 返回当前信号量的计数值。 int:当前计数值
  1. 代码示例
cpp 复制代码
#include <QCoreApplication> // 引入Qt核心应用程序模块
#include <QThread>          // 引入Qt线程模块
#include <QSemaphore>       // 引入Qt信号量模块
#include <QDebug>           // 引入Qt调试输出模块

// 创建一个信号量,初始计数为3,表示最多同时允许3个线程访问资源
QSemaphore semaphore(3);

class Worker : public QThread {
public:
    void run() override { // 重写QThread的run()方法,定义线程执行的代码
        // 尝试在1000毫秒内获取一个资源
        if (semaphore.tryAcquire(1, 1000)) { // 尝试获取一个资源,如果在超时内未能获取,则返回false
            qDebug() << "Thread" << QThread::currentThreadId() << "acquired a resource."; // 打印线程获取资源的消息
            QThread::sleep(2); // 模拟工作,线程休眠2秒
            qDebug() << "Thread" << QThread::currentThreadId() << "finished working."; // 打印线程完成工作的消息
            semaphore.release(); // 释放一个资源,使其他等待的线程可以获取到资源
        } else {
            qDebug() << "Thread" << QThread::currentThreadId() << "could not acquire a resource within timeout."; // 打印超时未获取资源的消息
        }

        // 显示信号量的状态
        qDebug() << "Current available resources:" << semaphore.available(); // 打印当前可用资源的数量
        semaphore.setCount(5); // 修改信号量的计数器值为5,增加可用资源
        qDebug() << "Max count:" << semaphore.maximumCount(); // 打印信号量的最大计数值
        qDebug() << "Current count:" << semaphore.currentCount(); // 打印当前信号量的计数值
    }
};

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv); // 创建Qt应用程序实例

    // 创建多个Worker线程实例
    Worker worker1, worker2, worker3, worker4, worker5;

    // 启动线程
    worker1.start();
    worker2.start();
    worker3.start();
    worker4.start();
    worker5.start();

    // 等待所有线程完成
    worker1.wait();
    worker2.wait();
    worker3.wait();
    worker4.wait();
    worker5.wait();

    return a.exec(); // 进入Qt事件循环
}

QT睡眠程序

  • 用于让当前线程休眠或延迟执行
  1. QThread::sleep(int sec);//个方法使当前线程休眠指定的秒数。
  2. QThread::msleep(int msec);//这个方法使当前线程休眠指定的毫秒数。
  3. QThread::usleep(int usec);//这个方法使当前线程休眠指定的微秒数。
相关推荐
用户805533698034 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner4 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz9 天前
QML Hello World 入门示例
qt
xcyxiner12 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner13 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner13 天前
DicomViewer (添加模型类)3
qt
xcyxiner14 天前
DicomViewer (目录调整) 2
qt
xcyxiner14 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00616 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术16 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript