【Qt开发】多线程QThread(通过QObject::moveToThread)和QMutex互斥锁的配置和基本函数

news2024/11/26 14:55:48

【Qt开发】多线程QThread(通过QObject::moveToThread)和QMutex互斥锁的配置和基本函数

多线程

Qt官方给了两种方法连运行多线程函数
一种是直接用QThread的run()方法
还有一种就是继承QObject 用moveToThread方法去放到QThread里执行

在官方文档中 推荐使用后者 前者是Qt4之前的版本用的

QThread

使用QThread需要自己定义一个继承QThread的类(最简单)
比如QThread的写法一般是:

class WorkerThread : public QThread 
{ 
/* 用到信号槽即需要此宏定义 */ 
Q_OBJECT 
public: 
/* 重写run方法,继承QThread的类,只有run方法是在新的线程里 */ 
	void run() override 
	{ 
         QString result = "线程开启成功";
         qDebug()<<result<<endl; 
	}

在这里需要加上override 关键字重写run函数(在定义时 加不加都可以 但推荐加 具体看编译器标准)
调用时 则直接用start方法即可以多线程方式运行run函数

 if (!workerThread->isRunning()) 
 	{
          workerThread->start(); 
          }

另外 可以用信号和槽函数来发送线程执行完成的信号或结果

 signals: 
     /* 声明一个信号,译结果准确好的信号 */ 
     void resultReady(const QString &s); 
  };

重写的run函数加上发送信号 最后这个result会在主线程中打印

	void run() override 
	{ 
         QString result = "线程开启成功";
         emit resultReady(result);
	}
 

在大类下用信号槽连接和槽函数

connect(workerThread, SIGNAL(resultReady(QString)),this, SLOT(handleResults(QString)));

void MainWindow::handleResults(const QString &result) 
{ 
   /* 打印出线程发送过来的结果 */ 
    qDebug()<<result<<endl; 
} 

最简单的QThread示例:

#ifndef WORKER_H
#define WORKER_H
 
#include <QThread>
 
class Worker : public QThread
{
public:
    Worker();
 
    void run();
 
    void printFunc();
 
 
};
 
#endif // WORKER_H

主函数调用:

#include <iostream>
#include <QDebug>
#include "Worker.h"
 
using namespace std;
 
int main()
{
    Worker w;
 
    w.start();
 
    qDebug()<<"主线程ThreadID: "<<QThread::currentThreadId();
 
    w.printFunc();
 
    while (1)
    {
 
    }
 
    return 0;
}

w.start();调用后就是用多线程运行run函数内容
而成员函数w.printFunc();则继续在主线程中执行

线程关闭则用如下语句:

	this->workerThread->quit();
        if(workerThread->wait())
        {

        }  

先quit再用wait等待 wait可以传参 表示延时多长时间
可以将关闭函数放在析构函数中执行

QObject::moveToThread

采用这个方法 需要继承QObject类
然后将其使用moveToThread方法移到一个线程里面执行

比如继承QObject类的Worker类
下面有一系列成员函数work1 work2等

moveToThread方法需要传入一个QThread地址来调用
表明把任务移动到该地址的QThread执行

这里可以把不同的工作函数传入同一个QThread执行(不同时间调用不同的工作)
也可以把同一个工作函数传入多个QThread执行(需要定义多个QThread)

对于管理多个线程 比较方便 但对于单一的线程 直接用QThread常规方法即可

通过主线程中的一个槽函数来连接

connect(this, SIGNAL(startWork(QString)), Worker_1, SLOT(Judg_doWork(QString)));

其中 startWork是信号

signals:
    void startWork(const QString &);

同样调用时 需要通过start方法先开启移动后的QThread
然后发送槽函数信号

    bool startThread(void)
    {
        if(!workerThread->isRunning())
        {
            workerThread->start();
            return true;
        }
        return false;
    }
            Worker_1->startThread();
            emit this->startWork("starWork\n");

startWork信号发送后 即跳转到Judg_doWork(QString)中执行
然后我们写个判断就可以进行传参了
其中 Judg_doWork也要声明为槽函数

public slots:
	void Judg_doWork(const QString &parameter)
    {
        this->isCanRun=true;
        //执行
        doWork(parameter);
    }
public:
	void doWork(const QString &parameter)
    {

        if(parameter=="")
        {
            QMutexLocker locker(&this->lock);
            return;
        }
        else
        {
            while(this->isCanRun)
            {
                QMutexLocker locker(&this->lock);
                QThread::msleep(200);
                qDebug()<<"开启线程"<<QThread::currentThreadId()<<"\n";
            }
            return;
        }
    }

关闭函数一样 但要加一个
可以将关闭函数放在析构函数中执行

另外 还需要将线程本身的finished()信号连接到deleteLater()在这里插入代码片函数

        connect(workerThread, SIGNAL(finished()),this, SLOT(deleteLater()));
        connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));

我自己写的关于QObject的类如下:

class MY_Thread_Worker;
class MY_Thread_Worker : public QObject
{
    Q_OBJECT
private:
    QMutex lock;
    bool isCanRun;

public slots:
    void Judg_doWork(const QString &parameter)
    {
        this->isCanRun=true;
        //执行
        doWork(parameter);
    }

public:
    QThread *workerThread;

    void doWork(const QString &parameter)
    {

        if(parameter=="")
        {
            QMutexLocker locker(&this->lock);
            return;
        }
        else
        {
            while(this->isCanRun)
            {
                QMutexLocker locker(&this->lock);
                QThread::msleep(200);
                qDebug()<<"开启线程"<<QThread::currentThreadId()<<"\n";
            }
            return;
        }
    }

    void stopWork(void)
    {
        QMutexLocker locker(&this->lock);
        this->isCanRun = false;
    }

    bool startThread(void)
    {
        if(!workerThread->isRunning())
        {
            workerThread->start();
            return true;
        }
        return false;
    }

    bool stopThread(void)
    {
        if(workerThread->isRunning())
        {
            stopWork();
            return true;
        }
        return false;
    }

    void closeThread(void)
    {
        stopWork();
        this->workerThread->quit();
        if(workerThread->wait())
        {
            
        }        
    }

    MY_Thread_Worker(QThread * worker_Thread = nullptr)
    {

        if(worker_Thread==nullptr)
        {
            this->workerThread = new QThread;
        }
        else
        {
            this->workerThread = worker_Thread;
        }
        this->moveToThread(workerThread);
        this->stopWork();
        connect(workerThread, SIGNAL(finished()),this, SLOT(deleteLater()));
        connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
        // connect(this, SIGNAL(startWork(QString)), worker, SLOT(Judg_doWork(QString)));  主窗口发送函数
        // connect(worker, SIGNAL(resultReady(QString)),this, SLOT(handleResults(QString)));  主窗口接收函数
    }

    ~MY_Thread_Worker()
    {
        closeThread();
    }
};

#endif // MY_QT_DEF_H

调用方式:

Worker_1 = new MY_Thread_Worker(new QThread);
    connect(this, SIGNAL(startWork(QString)), Worker_1, SLOT(Judg_doWork(QString)));
    qDebug()<<"主线程"<<QThread::currentThreadId()<<"\n";

Worker_1->startThread();
emit this->startWork("starWork\n");

其中 isCanRun变量是我定义用来判断线程状态的 本质上也可以作为一个软件锁来使用 但是我也加了QMutex锁

多线程测试和QThread::currentThreadId()

QThread::currentThreadId()可以查看当前程序的线程地址(主线程也可以用)
进入多线程前 打印一次主线程
在这里插入图片描述

然后我开了两个定时器分别0.5s和1s 子线程则200ms打印一次
在这里插入图片描述
可以看到 定时器都属于主线程里面的
而子线程则不一样

QMutex互斥锁和线程同步

为了避免多次调用(除非你想)或者多线程访问共享资源时打架(除非你想) 则需要引入线程锁
线程锁的作用就是在运行时上锁 运行后释放 如果运行时检测到锁了 则不执行 直到锁被释放后继续执行
通过声明QMutex变量即可使用lock方法加锁
可以在不同的线程中使用 以达到同步的作用(需要全局变量)
也可以在一个线程中使用防止被多次调用

比如:

    static QMutex MessageOutput_Mutex;
    MessageOutput_Mutex.lock();

结束后解锁则调用

        MessageOutput_Mutex.unlock();

比如例子:

#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QMutex>
 
// 定义共享资源
int sharedValue = 0;
QMutex mutex;
 
// 定义一个线程类
class MyThread : public QThread
{
public:
    void run() override {
        for(int i = 0; i < 5; i++) {
            mutex.lock(); // 加锁
            sharedValue++; // 访问共享资源
            qDebug() << "Thread ID: " << QThread::currentThreadId() << " - Shared Value: " << sharedValue;
            msleep(1000); // 线程休眠1秒
            mutex.unlock(); // 解锁
        }
    }
};
 
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
 
    MyThread thread1;
    MyThread thread2;
 
    thread1.start();
    thread2.start();
 
    thread1.wait();
    thread2.wait();
 
    qDebug() << "Final Shared Value: " << sharedValue;
 
    return a.exec();
}

执行效果:
在这里插入图片描述
如果不加锁则会打架
在这里插入图片描述
另外 QMutexLocker提供了一种更加便捷的方式
通过传入QMutex变量来进行上锁

QMutexLocker locker(&this->lock);

而在程序退出后会自动解锁
此方法可以在循环里面调用 每次循环开始时调用即可

附录:C语言到C++的入门知识点(主要适用于C语言精通到Qt的C++开发入门)

C语言与C++的不同

C语言是一门主要是面向工程的语言
C++则是面向对象

C语言中 某些功能实现起来较为繁琐
比如结构体定义:

一般写作:

typedef struct stu_A
{
}A;

也可以写作:

typedef struct 
{
}A;

但 大括号后面的名称是不可省去的

不过 C++的写法就比较简单
除了支持上述写法外

也支持直接声明

typedef struct A
{
}

另外 C++是完全支持C语言库和语法的
不过C++里面的库也有些很方便的高级功能用法 只不过实现起来可能不如C的速度快

再者 C语言与C++的编译流程不一样
C语言没有函数重载 所以给编译器传参就是直接传函数名称
但是C++除了传函数名称外 还会穿函数的参数、类型等等 以实现函数重载

C++中写C语言代码

上文提到 C++可以完全兼容C的写法
但是编译流程也还是不一样
所以如果在编译层面进行C语言代码编译 则通常用以下方法:

extern "C"
{
...
}

表面大括号内的内容用C的方法进行编译

另外 如果还是用C++的编译器 但要实现C语言函数 则需要用到C语言的库

在C语言中 我们一般用如下方法导入库

#include <stdio.h>

此方法同样适用于C++ 但是C++可以更方便的写成去掉.h的方式
比如:

#include <iostream>

在C++中 为了调用C语言的库 可以采用在原库名称前加一个"c"的方式导入
如:

#include <cstdio>

这样就可以使用printf等函数了 甚至比C++的std方法更快

C语言到C++的知识点

在这里插入图片描述

Qt开发中需要了解的C++基础知识

namespace

C++面向对象的特性下诞生的一个名称
表示某个函数、变量在某个集合下 用作namespace
比如 <iostream>库中的关键字cin在std下 则写作std::cin
std就是namespace
::表示某空间下的某某
前面是空间名称 后面是变量、函数名称

using namespace可以告诉编译器以下都用xx名称空间
比如:

using namespace std;
cout<<"a";

如果没有告诉编译器所使用的空间名称 则要写成:

std::cout<<"a";

同样 可以自定义某一段代码属于哪个空间:

namespace xx
{
...
}

输入输出

在C++中 用iostream作为输入输出流的库

#include <iostream>

用cin和cout关键字进行输入和输出
如:

using namespace std;
int a=0;
cin>>a; //输入到a

cout<<a;  //输出a

类比scanf和printf
同样 还有一个关键字endl表示换行
cout和cin的传参是不固定的
由编译器自行裁定

字符串类型

在C语言中 常用char *表示字符串
但是在C++中 可以直接用string类型
比如:

char * s="456";
string str="123";

由于cout的特性 这两种字符串都可以直接打印
但如果使用C语言中printf的打印方式时 采用%s方式打印字符串 则不能传入string类型

class类

C++的核心就是class
同Python等支持面向对象的语言一样
可以理解成一个支持函数、继承、自动初始化、销毁的结构体
在class类中 有private私有、public公有变量
前者只能内部访问 后者可以外部调用使用
如:

class A
{
public:
int a;
private:
int b;
}

a可以用A.a的方式方位 b则外部无法访问

构造函数和析构函数(解析函数)

构造函数可以理解成对类的初始化 反之析构函数则是退出时进行销毁前的函数
两者需要与类的名称相同 析构函数则在前面加一个~表示非
如:

class A
{
public:
int a;
A();
~A();
private:
int b;
}

A::A()
{
...
}

A::~A()
{
...
}

构造函数可以定义传参 析构函数则不行

类的继承

如果有两个类A和B 想让A里面包含B 则可以写作继承的写法
继承后 A类的变量可以直接调用B下面的成员
如:

class B
{
int b;
}
class A: public B
{
int a;
}

在定义A后 可以访问到B的成员b 当然 继承也可以私有

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

6.MySQL的增删改查

目录 Create 单行插入数据 全列插入 多行数据指定列插入 插入否则更新 主键冲突 唯一键冲突 &#xff08;☆&#xff09; 替换数据 Retrieve Select列 全列查询 指定列查询 查询字段为表达式 where条件 NULL 的查询 NULL 和 NULL 的比较&#xff0c; 和 <>…

野蛮生长后,AI大模型还需要重复“造轮子”?

AI大模型带来的智能革命&#xff0c;媲美工业革命和电力革命&#xff0c;深刻改变人类社会的生产生活方式&#xff0c;是开启智能时代的那一台“蒸汽机”。 从文生文到文生图&#xff0c;再到文生视频&#xff0c;以ChatGPT、Sora等为代表的大模型引领了全球人工智能技术与产业…

《使用深度学习三分类模型预测胸部CT中的肺腺癌侵袭性》| 文献速递-基于深度学习的乳房、前列腺疾病诊断系统

Title 题目 Predicting Invasiveness of Lung Adenocarcinoma at Chest CT with Deep Learning Ternary Classification Models 《使用深度学习三分类模型预测胸部CT中的肺腺癌侵袭性》 Background 背景 Preoperative discrimination of preinvasive, minimally invasive,…

开关电源之结构分析

如有技术问题及技术需求请加作者微信! 开关电源之结构分析 1、开关电源的结构 常用开关电源,主要是为电子设备提供直流电源供电。电子设备所需要的直流电压,范围一般都在几伏到十几伏,而交流市电电源供给的电压为220V(110V),频率为50Hz(60Hz)。开关电源的作用就是把一…

Surface Studio 曾经耀眼的骚气光芒至今依然

Surface Studio 曾经耀眼的骚气光芒至今依然 Surface Studio当时上市价格25988RMB&#xff0c;现在小黄鱼大概在3000的样子可以买到屏幕没有老化的那种。看着那块巨大的5K触摸屏&#xff0c;简直让人垂涎三尺欲罢不能。 一、卓越配置&#xff0c;尽显强大性能 Surface Studio…

freeRTOS之任务调度

本节课的内容是重中之重&#xff0c;对复习操作系统的任务调度也很有帮助。

API接口自定义字段返回,最终解决方案,再也不用写 vo、dto 转换逻辑了

西湖的晚霞真美啊 前言 此套方案太过完美&#xff0c;唯一缺点就是&#xff0c;字段返回基于反射操作&#xff0c;损失一点点性能 效果 API接口指定使用自定义增强注解MoreSerializeField&#xff0c;标注只返回 departName 字段 前端只会接收到 departName 字段 实现原理 …

Python爬虫——Selenium方法爬取LOL页面

文章目录 Selenium介绍用Selenium方法爬取LOL每个英雄的图片及名字 Selenium介绍 Selenium 是一个用于自动化Web应用程序测试的工具&#xff0c;但它同样可以被用来进行网页数据的抓取&#xff08;爬虫&#xff09;。Selenium 通过模拟用户在浏览器中的操作&#xff08;如点击…

进程间通信---管道通信、命名管道、匿名管道详解

什么是通信&#xff1f; 为什么要通信&#xff1f; 如何做到通信&#xff1f; 管道是什么&#xff1f; 管道用来干什么&#xff1f; 管道如何实现通信&#xff1f; 匿名管道是什么&#xff1f; 如何实现&#xff1f; 命名管道是什么&#xff1f; 如何实现&#xff1f; 什么是文…

探究 Element Plus Menu 横向多层级展开组件的 Bug 及解决方案

文章目录 1 ellipsis 是否省略多余的子项&#xff08;仅在横向模式生效&#xff09;问题描述解决方案 2 多个级别的子菜单位置错乱或默认直接展开问题描述解决方案1 index没有设置2 通用策略 1 ellipsis 是否省略多余的子项&#xff08;仅在横向模式生效&#xff09; 问题描述…

Global Structure-from-Motion Revisited golmap论文翻译

Global Structure-from-Motion Revisited Paper&#xff1a;https://demuc.de/papers/pan2024glomap.pdf Code&#xff1a;https://github.com/colmap/glomap 摘要- 从图像中恢复三维结构和摄像机运动一直是计算机视觉研究的热点&#xff0c;被称为运动结构恢复(SfM)。这个问…

【RAG提升技巧】查询改写HyDE

简介 提高 RAG 推理能力的一个好方法是添加查询理解层 ——在实际查询向量存储之前添加查询转换。以下是四种不同的查询转换&#xff1a; 路由&#xff1a;保留初始查询&#xff0c;同时查明其所属的适当工具子集。然后&#xff0c;将这些工具指定为合适的选项。查询重写&…

[论文笔记]ZeRO: Memory Optimizations Toward Training Trillion Parameter Models

引言 今天带来ZeRO: Memory Optimizations Toward Training Trillion Parameter Models的论文笔记。 大型深度模型提供了显著的准确性提升&#xff0c;但训练数十亿到数万亿个参数是具有挑战性的。现有的解决方案&#xff0c;如数据并行和模型并行&#xff0c;存在基本的局限…

汇昌联信做拼多多电商如何提升浏览量?

在拼多多电商平台上&#xff0c;如何提升商品的浏览量是每个卖家都关注的问题。高浏览量不仅意味着更多的潜在客户&#xff0c;还能间接提升销量和店铺的知名度。汇昌联信作为电商运营者&#xff0c;需要采取有效策略来吸引消费者的注意力。 一、优化商品标题和描述 商品标题是…

LDR6328Q:重塑电源管理新境界的取电芯片

在电子设备日益普及的今天&#xff0c;高效、智能的电源管理成为了提升用户体验的关键因素之一。而LDR6328Q作为一款专为设备端设计的取电芯片&#xff0c;凭借其出色的性能和创新的技术&#xff0c;正逐步成为电源管理领域的一颗璀璨新星。本文将深入探讨LDR6328Q取电芯片的特…

大模型面经,不要到处找了,收藏我这一篇就够了

在当前技术快速发展的背景下&#xff0c;大模型领域的职位成为了许多求职者的热门选择。为了帮助大家更好地准备面试&#xff0c;这里整理了一份大模型面试经验分享&#xff0c;涵盖了一些常见的面试流程、可能遇到的技术问题以及面试官可能会问到的行为问题等。 大模型面试经…

什么是实时数据仓库? 优势与最佳实践

在当今数据驱动的世界中&#xff0c;许多企业使用实时数据仓库来满足其分析和商业智能 (BI) 需求。这使他们能够做出更好的决策、推动增长并为客户提供价值。 数据仓库是一种数据存储和管理系统&#xff0c;其设计目标只有一个&#xff1a;管理和分析数据&#xff0c;以实现商…

链表求和问题(面试题)

目录 一题目&#xff1a; 二思路汇总&#xff1a; 三解答代码&#xff1a; 一题目&#xff1a; leetcode题目链接 &#xff1a;面试题 02.05. 链表求和 - 力扣&#xff08;LeetCode&#xff09; 二思路汇总&#xff1a; 想到这道题&#xff0c;可能看起来第一思路就是把它们…

【C++二分查找】2563. 统计公平数对的数目

本文涉及的基础知识点 C二分查找 LeetCode2563. 统计公平数对的数目 给你一个下标从 0 开始、长度为 n 的整数数组 nums &#xff0c;和两个整数 lower 和 upper &#xff0c;返回 公平数对的数目 。 如果 (i, j) 数对满足以下情况&#xff0c;则认为它是一个 公平数对 &…

利用住宅代理优化媒体监控,全面提升品牌管理与市场竞争力

引言 什么是媒体监控&#xff1f;主要用于哪里&#xff1f; 媒体监控面临的主要挑战 住宅代理在媒体监控中的作用 如何利用住宅代理进行媒体监控 总结 引言 在信息化时代&#xff0c;媒体监控&#xff08;media monitoring&#xff09;已成为企业进行品牌声誉管理、市场研…