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);//这个方法使当前线程休眠指定的微秒数。
相关推荐
寒笙LED7 分钟前
C++详细笔记(六)string库
开发语言·c++·笔记
IT书架14 分钟前
golang面试题
开发语言·后端·golang
初遇你时动了情31 分钟前
uniapp 城市选择插件
开发语言·javascript·uni-app
zongzi_4941 小时前
二次封装的天气时间日历选择组件
开发语言·javascript·ecmascript
kikyo哎哟喂2 小时前
Java 代理模式详解
java·开发语言·代理模式
duration~2 小时前
SpringAOP模拟实现
java·开发语言
一条晒干的咸魚2 小时前
【Web前端】实现基于 Promise 的 API:alarm API
开发语言·前端·javascript·api·promise
就爱六点起2 小时前
C/C++ 中的类型转换方式
c语言·开发语言·c++
我明天再来学Web渗透2 小时前
【SQL50】day 2
开发语言·数据结构·leetcode·面试
猫猫的小茶馆2 小时前
【C语言】指针常量和常量指针
linux·c语言·开发语言·嵌入式软件