系统讨论Qt的并发编程2——介绍一下Qt并发的一些常用的东西

目录

QThreadPool与QRunnable

互斥机制:QMutex, QMutexLocker, QSemaphore, QWaitCondition

跨线程的通信

入门QtConcurrent,Qt集成的一个并发框架

一些参考


QThreadPool与QRunnable

QThreadPool自身预备了一些QThread。这样,我们就不需要频繁的创建和销毁我们的QThread,从而提升了性能。每个 Qt 应用程序都有一个全局 QThreadPool 对象,可以通过调用 globalInstance() 来访问。

那么,我们应该如何启动QThreadPool呢?答案是——使用一个接口叫做QRunnable。QRunnable自身只是要求你实现一个接口。那就是run接口。QRunnable自身不是一个QObject,意味着他是没有信号与槽的,适合那些跟Qt关系不大的,或者说跨线程之间没有对象交互的场景。

#include "Counter.h"
#include <QDebug>
#include <QRandomGenerator>
#include <QThread>
Counter::Counter() {
}
​
void Counter::run() {
    qInfo() << "Starting the job!";
    for (int i = 0; i < 20; i++) {
        qInfo() << QThread::currentThread() << "" << i;
        auto sleep_time = QRandomGenerator::global()->bounded(1, 100);
        QThread::msleep(sleep_time);
    }
    qInfo() << "Job done!";
}

在我们的主线程中,直接将Runable的子类对象传递给我们的QThreadPool,我们的QThreadPool回直接调用内部的run函数执行:

#include "Counter.h"
#include <QCoreApplication>
#include <QThreadPool>
​
int main(int argc, char* argv[]) {
    QCoreApplication a(argc, argv);
    QThread::currentThread()->setObjectName("Main");
​
    QThreadPool* pool = QThreadPool::globalInstance();
    qInfo() << pool->maxThreadCount();
​
    for (int i = 0; i < 100; i++) {
        Counter* counter = new Counter();
        counter->setAutoDelete(true);
        pool->start(counter);
    }
​
    return a.exec();
}

现在就可以了,可以自己拷贝看看效果。

互斥机制:QMutex, QMutexLocker, QSemaphore, QWaitCondition

oh天,简直就跟报菜名一样,这四个东西都是用来保证互斥机制的。分别对应了我们的std::mutex. std::mutex_guard, std::counting_semaphore和std::condition_variable四个东西。

关于锁等并发编程,这里笔者不打算重复说明了。笔者有一个专门讲述高级并发编程的模块,感兴趣的朋友可以看看:

高阶开发基础——目录部分-CSDN博客

一个简单的demo

#include <QCoreApplication>
#include <QDebug>
#include <QList>
#include <QMutex>
#include <QMutexLocker>
#include <QSemaphore>
#include <QThread>
#include <QWaitCondition>
​
const int      BufferSize = 5;         // 缓冲区大小
QList<int>     buffer;                 // 共享缓冲区
QMutex         mutex;                  // 用于保护共享缓冲区
QSemaphore     freeSpace(BufferSize);  // 空闲空间信号量
QSemaphore     usedSpace(0);           // 已使用空间信号量
QWaitCondition bufferNotEmpty;         // 缓冲区非空条件
QWaitCondition bufferNotFull;          // 缓冲区未满条件
​
// 生产者线程
class Producer : public QThread {
protected:
    void run() override {
        for (int i = 0; i < 10; ++i) {
            freeSpace.acquire();  // 等待空闲空间
            mutex.lock();         // 锁定互斥量
​
            // 如果缓冲区已满,等待缓冲区未满条件
            if (buffer.size() == BufferSize) {
                bufferNotFull.wait(&mutex);
            }
​
            buffer.append(i);  // 生产数据
            qDebug() << "Produced:" << i;
​
            mutex.unlock();       // 解锁互斥量
            usedSpace.release();  // 增加已使用空间
​
            // 通知消费者缓冲区非空
            bufferNotEmpty.wakeAll();
        }
    }
};
​
// 消费者线程
class Consumer : public QThread {
protected:
    void run() override {
        for (int i = 0; i < 10; ++i) {
            usedSpace.acquire();  // 等待已使用空间
            mutex.lock();         // 锁定互斥量
​
            // 如果缓冲区为空,等待缓冲区非空条件
            if (buffer.isEmpty()) {
                bufferNotEmpty.wait(&mutex);
            }
​
            int value = buffer.takeFirst();  // 消费数据
            qDebug() << "Consumed:" << value;
​
            mutex.unlock();       // 解锁互斥量
            freeSpace.release();  // 增加空闲空间
​
            // 通知生产者缓冲区未满
            bufferNotFull.wakeAll();
        }
    }
};
​
int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);
​
    Producer producer;
    Consumer consumer;
​
    producer.start();  // 启动生产者线程
    consumer.start();  // 启动消费者线程
​
    producer.wait();  // 等待生产者线程结束
    consumer.wait();  // 等待消费者线程结束
​
    return a.exec();
}

跨线程的通信

所以Qt如何完成跨线程的通信,即跨线程的信号与槽呢?答案是在使用QueueConnection,当然AutoConnection也可以,Qt6中会默认判断两个对象是否在同一个线程,不再的话就会自动选择QueueConnection。这个是将我们的信号与槽机制放置到了事件的监听循环队列当中去了。

#include <QCoreApplication>
#include <QDebug>
#include <QObject>
#include <QThread>
​
// 工作线程类
class Worker : public QObject {
    Q_OBJECT
​
public:
    explicit Worker(QObject *parent = nullptr) : QObject(parent) {
    }
​
public slots:
    void doWork() {
        qDebug() << "Worker: Running in thread" << QThread::currentThreadId();
​
        // 模拟耗时操作
        QThread::sleep(2);
​
        // 发送完成信号
        emit workDone();
    }
​
signals:
    void workDone();  // 工作完成信号
};
​
// 主线程类
class Controller : public QObject {
    Q_OBJECT
​
public:
    explicit Controller(QObject *parent = nullptr) : QObject(parent) {
    }
​
    void start() {
        qDebug() << "Controller: Running in thread"
                 << QThread::currentThreadId();
​
        // 创建工作线程
        QThread *workerThread = new QThread;
        Worker  *worker       = new Worker;
​
        // 将 Worker 移动到工作线程
        worker->moveToThread(workerThread);
​
        // 连接信号与槽
        connect(workerThread, &QThread::started, worker, &Worker::doWork);
        connect(worker, &Worker::workDone, this, &Controller::handleWorkDone);
        connect(worker, &Worker::workDone, workerThread, &QThread::quit);
        connect(workerThread, &QThread::finished, worker, &Worker::deleteLater);
        connect(workerThread, &QThread::finished, workerThread,
                &QThread::deleteLater);
​
        // 启动工作线程
        workerThread->start();
    }
​
public slots:
    void handleWorkDone() {
        qDebug() << "Controller: Work done, running in thread"
                 << QThread::currentThreadId();
    }
};
​
int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);
​
    Controller controller;
    controller.start();  // 启动控制器
​
    return a.exec();
}
​
#include "main.moc"
    1. 这里#include "main.moc"是一个小的偷懒技巧,QT的元对象是依赖于元对象处理器的,我们include进来qt处理后的结果才好方便解决未定义的符号问题

  • 2. Worker::workDone 信号是从工作线程发射的,而 Controller::handleWorkDone 槽是在主线程中执行的。

    1. 由于信号与槽的连接类型是 Qt::QueuedConnection(默认跨线程连接类型),信号会被放入主线程的事件循环中,由主线程异步执行槽函数。

入门QtConcurrent,Qt集成的一个并发框架

为什么会有QtConcorrent呢?因为大部分情况下,我们可以不需要太客制化的并发(特殊场景除外)。我们总是希望将任务并行化但是不想要关心其具体的细节。

QtConcurrent就将并发进一步的提升为了一个经典的异步框架。我们只需要让一个任务并发的执行出去,而不需要关心它如何执行,在另一个时间点上我调用一个wait机制就把东西取回来(如果已经做完了直接返回,没有做完那就阻塞的等待直到做完了为之)

返回值函数签名
QFuture<T>run(Function function, ...)
QFuture<T>run(QThreadPool *pool, Function function, ...)

这里,我们的Run就是直接的派发一个任务出去。返回回来的QFuture就是我们希望得到的一个结果。比如说,我们depatch了一个网络请求。需要其结果的时候,我们的结果就应该被存放在我们的QFuture里面。

直到我们获取的时候,我们才会获取我们想要的东西。我们调用的是waitForFinish这个函数,明确的表达我们需要结果后才会执行。

简单的说:QFuture 允许线程与一个或多个将在稍后某个时间点准备就绪的结果同步。结果可以是任何具有默认、复制和可能移动构造函数的类型。如果在调用 result()、resultAt()、results() 和 takeResult() 函数时结果不可用,QFuture 将等待,直到结果可用。您可以使用 isResultReadyAt() 函数来确定结果是否已准备就绪。对于报告多个结果的 QFuture 对象,resultCount() 函数返回连续结果的数量。这意味着从 0 到 resultCount() 迭代结果始终是安全的。takeResult() 会使未来无效,并且任何后续尝试访问未来结果的尝试都会导致未定义的行为。isValid() 告诉您是否可以访问结果。

QFuture 提供了 Java 样式的迭代器 (QFutureIterator) 和 STL 样式的迭代器 (QFuture::const_iterator)。使用这些迭代器是访问未来结果的另一种方式。

如果需要将一个异步计算的结果传递给另一个异步计算,QFuture 提供了一种使用 then() 链接多个顺序计算的便捷方法。onCanceled() 可用于添加在 QFuture 被取消时要调用的处理程序。此外,onFailed() 可用于处理链中发生的任何故障。请注意,QFuture 依赖于异常来进行错误处理。如果无法使用异常,您仍然可以通过将错误类型作为 QFuture 类型的一部分来指示 QFuture 的错误状态。例如,您可以使用 std::variant、std::any 或类似类型来保存结果或失败,或者创建自定义类型。

上面的部分是翻译Qt文档的结果。

如果需要使用到信号与槽的机制,我们还会需要使用的是QFutureWatcher来监控我们的QFuture的状态,这样,信号与槽机制就可以跟异步框架精密协调的工作起来了。

QFutureWatcher 中还提供了一些 QFuture 函数:progressValue()、progressMinimum()、progressMaximum()、progressText()、isStarted()、isFinished()、isRunning()、isCanceled()、isSuspending()、isSuspended()、waitForFinished()、result() 和 resultAt()。cancel()、setSuspended()、suspend()、resume() 和 toggleSuspended() 函数是 QFutureWatcher 中的插槽。 状态更改通过 started()、finished()、canceled()、suspending()、suspended()、resumed()、resultReadyAt() 和 resultsReadyAt() 信号报告。进度信息由 progressRangeChanged()、void progressValueChanged() 和 progressTextChanged() 信号提供。 节流控制由 setPendingResultsLimit() 函数提供。当待处理的 resultReadyAt() 或 resultsReadyAt() 信号的数量超过限制时,未来所代表的计算将自动受到限制。一旦待处理信号的数量低于限制,计算将恢复。

以及,Qt自身也提供了一个类似域LockGuard的机制,这里,我们的类名称是QFutureSynchronizer,可以看看这里的Qt文档。

一些参考

  • Qt 6 Core Advanced with C++ | Udemy

  • Qt Documentations Qt Documentation | All Documentation

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/980865.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

IO基础知识和练习

一、思维导图 二、练习 1.使用标准IO函数&#xff0c;实现文件的拷贝 #include <head.h> int main(int argc, const char *argv[]) {FILE *pfopen("./one.txt","r");FILE *fpfopen("./two.txt","r");if(pNULL)PRINT_ERROR(&qu…

Linux驱动开发之串口驱动移植

原理图 从上图可以看到RS232的串口接的是UART3&#xff0c;接下来我们需要使能UART3的收发功能。一般串口的驱动程序在内核中都有包含&#xff0c;我们配置使能适配即可。 设备树 复用功能配置 查看6ull如何进行uart3的串口复用配置&#xff1a; 设备树下添加uart3的串口复用…

2.css简介

什么是css&#xff1a; CSS (Cascading Style Sheets&#xff0c;层叠样式表&#xff09;&#xff0c;是一种用来为结构化文档&#xff08;如 HTML 文档或 XML 应用&#xff09;添加样式&#xff08;字体、间距和颜色等&#xff09;的计算机语言&#xff0c;CSS 文件扩展名为 .…

数据可视化02-PCA降维

一、PCA PCA做什么&#xff1f;找坐标系。 目标&#xff1f;二维降到一维&#xff0c;信息保留最多。 怎么样最好&#xff1f;数据分布最分散的方向&#xff08;方差最大&#xff09;&#xff0c;作为主成分&#xff08;坐标轴&#xff09;。 二、怎么找主成分&#xff1f; …

TVbox蜂蜜影视:智能电视观影新选择,简洁界面与强大功能兼具

蜂蜜影视是一款基于猫影视开源项目 CatVodTVJarLoader 开发的智能电视软件&#xff0c;专为追求简洁与高效观影体验的用户设计。该软件从零开始编写&#xff0c;界面清爽&#xff0c;操作流畅&#xff0c;特别适合在智能电视上使用。其最大的亮点在于能够自动跳过失效的播放地址…

【word】电子签名设置、保存和调用

设置电子签名&#xff1a;将扫描版或照片 转化为 word的电子签名 保存电子签名&#xff1a;将上述电子签名 存储到 word资料库中 调用电子签名&#xff1a;在正文中使用 快捷键 快速调用word电子签名 1. 设置电子签名 1.1 手写版签名 1.2 插入到word 插入 - 图片 1.3 着色效…

KVM虚拟机磁盘创建探究-1

在使用 virt-install 命令时&#xff0c;像 --disk path/var/lib/libvirt/images/vm1.qcow2,size20 这样的参数配置会自动创建指定路径和大小的磁盘镜像文件&#xff0c;不需要再单独使用 qemu-img 去创建。 详细解释 当你使用 virt-install 并指定 --disk 参数时&#xff0c…

第三十三:6.3. 【mitt】 任意组件通讯

概述&#xff1a;与消息订阅与发布&#xff08;pubsub&#xff09;功能类似&#xff0c;可以实现任意组件间通信。 // 引入mitt import mitt from "mitt";// 创建emitter const emitter mitt()/*// 绑定事件emitter.on(abc,(value)>{console.log(abc事件被触发,…

Android Stuido 调整左侧文件导航栏文字大小

Android Studio左侧文件导航栏文字大小默认比较小&#xff0c;这里记录下调整的路径&#xff1a; File-->Settings-->Appearance & Behavior-->Appearance-->勾上“Use custom font:” 就可以调整文字大小了&#xff0c;然后确定就好了。

大模型学习笔记------LLM模型开发流程

大模型学习笔记------LLM模型开发流程 1、总体开发流程2、各部分说明3、总结 LLM(Large Language Model)模型&#xff0c;即大型语言模型是大模型中极其重要的分支。它包含了GPT、BERT、Gemini、Qwen、Llama等&#xff0c;这些大模型衍生了相当多的各种改进版本。这些大模型的开…

剑指 Offer II 040. 矩阵中最大的矩形

comments: true edit_url: https://github.com/doocs/leetcode/edit/main/lcof2/%E5%89%91%E6%8C%87%20Offer%20II%20040.%20%E7%9F%A9%E9%98%B5%E4%B8%AD%E6%9C%80%E5%A4%A7%E7%9A%84%E7%9F%A9%E5%BD%A2/README.md 剑指 Offer II 040. 矩阵中最大的矩形 题目描述 给定一个由 …

【含文档+PPT+源码】基于SpringBoot+Vue医药知识学习与分享平台的设计与实现

项目介绍 本课程演示的是一款 基于SpringBootVue医药知识学习与分享平台的设计与实现&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运…

基于提示驱动的潜在领域泛化的医学图像分类方法(Python实现代码和数据分析)

摘要 医学图像分析中的深度学习模型易受数据集伪影偏差、相机差异、成像设备差异等导致的分布偏移影响&#xff0c;导致在真实临床环境中诊断不可靠。领域泛化&#xff08;Domain Generalization, DG&#xff09;方法旨在通过多领域训练提升模型在未知领域的性能&#xff0c;但…

【监督学习】支持向量机步骤及matlab实现

支持向量机 &#xff08;四&#xff09;支持向量机1.算法步骤2. MATLAB 实现参考资料 &#xff08;四&#xff09;支持向量机 支持向量机&#xff08;Support Vector Machine, SVM&#xff09;是一种用于分类、回归分析以及异常检测的监督学习模型。SVM特别擅长处理高维空间的…

数据集/API 笔记:湿球黑球温度(WBGT)观测数据

data.gov.sg WBGT是一个综合指标&#xff0c;考虑了气温、湿度、风速和太阳辐射&#xff0c;与气温不同。 报告的WBGT是过去15分钟内的平均值&#xff0c;每15分钟更新一次。 API 调用 curl --request GET \--url https://api-open.data.gov.sg/v2/real-time/api/weather …

基于 DataEase 的企业数据分析实践

1. 前言 在上一篇《基于 Selenium 实现的必应企业信息抓取工具》中&#xff0c;成功实现了对企业信息的批量抓取与导出。接下来&#xff0c;将对这些数据进行深入分析&#xff0c;包括地区分布、所属行业、规模大小等维度。其中&#xff0c;最直接的需求是统计每个省份的企业数…

教资信息技术之数据库技术

一、概述 1.1 基本概念 数据&#xff1a;描述事物的符号记录称为数据 数据库&#xff1a;长期存储在计算机内、有组织的、可共享的大量数据的集合。 数据库管理系统&#xff1a;位于用户和操作系统之间的一层数据管理软件 数据库系统&#xff1a;数据库系统是由数据库、数据…

JavaAPI(反射)

反射机制简介 获取一个类的实例对象&#xff0c;一般用new关键字来调用构造器获取实例&#xff0c;但是使用new有优点也有缺点。 优点&#xff1a; 性能高&#xff0c;JVM已经对这种调用进行了优化。不需要额外的权限检查、直接调用构造器获取实例、简单方便 缺点&#xff…

零信任沙箱:为网络安全筑牢“隔离墙”

在数字化浪潮汹涌澎湃的今天&#xff0c;网络安全如同一艘船在波涛汹涌的大海中航行&#xff0c;面临着重重挑战。数据泄露、恶意软件攻击、网络钓鱼等安全威胁层出不穷&#xff0c;让企业和个人用户防不胜防。而零信任沙箱&#xff0c;就像是一座坚固的“隔离墙”&#xff0c;…

开源嵌入式实时操作系统NuttX介绍

一、NuttX RTOS的发展历程&#xff1a;从个人项目到Apache顶级开源项目 NuttX 是一款轻量级、可扩展的实时操作系统&#xff08;RTOS&#xff09;&#xff0c;其发展历程堪称开源社区的经典案例。 起源与初创&#xff08;2003-2007&#xff09; NuttX 由 Gregory Nutt 于2003…