【C++】 Qt-线程并发与线程同步

news2024/12/25 12:19:07

文章目录

      • 线程并发
      • 线程同步
        • 原子访问(InterLocked)
        • 关键段(Critical_Section,也叫临界区)
          • 回顾单例出现的问题
          • 关键段基本使用
          • 封装关键段
        • Qt下的多线程
          • 多线程与进度条
          • Qt-QThread

线程并发

我们再创建一个控制台文件命名为main2.cpp,然后在这个文件中创建三条线程,在这三条中同时为一个全局变量进行递增操作,并在最后输出这个全局变量。

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    //线程句柄
    HANDLE handle1 = ::CreateThread(nullptr/*默认的安全属性*/,
                   0/*windows 默认1M*/,
                   &ThreadProc/*线程函数的地址*/,
                   nullptr/*线程函数的参数*/,
                   0/*0:立即运行 ,SUS_PEND 挂起*/,
                   nullptr/*线程ID*/);

    HANDLE handle2 = ::CreateThread(nullptr/*默认的安全属性*/,
                   0/*windows 默认1M*/,
                   &ThreadProc/*线程函数的地址*/,
                   nullptr/*线程函数的参数*/,
                   0/*0:立即运行 ,SUS_PEND 挂起*/,
                   nullptr/*线程ID*/);

    HANDLE handle3 = ::CreateThread(nullptr/*默认的安全属性*/,
                   0/*windows 默认1M*/,
                   &ThreadProc/*线程函数的地址*/,
                   nullptr/*线程函数的参数*/,
                   0/*0:立即运行 ,SUS_PEND 挂起*/,
                   nullptr/*线程ID*/);

    if(WAIT_OBJECT_0 == WaitForSingleObject(handle1,INFINITE)){
        if(handle1){
            ::CloseHandle(handle1);
            handle1 = nullptr;
        }
    }

    if(WAIT_OBJECT_0 == WaitForSingleObject(handle2,INFINITE)){
        if(handle2){
            ::CloseHandle(handle2);
            handle2 = nullptr;
        }
    }

    if(WAIT_OBJECT_0 == WaitForSingleObject(handle3,INFINITE)){
        if(handle3){
            ::CloseHandle(handle3);
            handle3 = nullptr;
        }
    }
    qDebug()<<"N============== "<<N;
    return a.exec();
}

线程函数以及全局变量

int N = 0;
//线程函数
DWORD WINAPI ThreadProc (LPVOID lpThreadParameter){

    for(int i=0;i<100;i++){
        N++;
        qDebug()<<"N= "<<N;
        Sleep(50);
    }
    return 0;
}

我们经过多次测试发现,最终结果并不一定是我们预想的300

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7ghGnBqj-1689425356251)(C++.assets/image-20230706045635228.png)]

这里出现的就是并发问题:

多个线程同时操作同一个资源(内存空间、文件句柄、网络句柄),可能会导致的结果不一致的问题。发生的前提条件一定是多个线程下共享资源。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IH7S7mWf-1689425356252)(C++.assets/image-20230706045839262.png)]

那么既然出现了问题,我们也会有相应的解决办法,就是接下来要说的线程同步

线程同步

线程同步,就是通过协调线程执行的顺序,避免多个线程同时操作同一个资源导致并发问题,使结果多次执行结果一致。

常见的线程同步方式:原子访问、关键段、事件、互斥量、条件变量、信号量。

上面提到的并发问题,解决方法有很多,重点学习 锁。

原子访问(InterLocked)

同一时刻,只允许一个线程访问一个变量。注意:他只是对一个变量保持原子自增、自减操作,对于一个代码段来说并不适用。

DWORD WINAPI ThreadProc (LPVOID lpThreadParameter){
    for(int i=0;i<100;i++){
        //N++;
        ::InterlockedIncrement((long*)&N);  //源自方式 递增 ++
        //N--;
        //::InterlockedDecrement((long*)&N);
        qDebug()<<"N= "<<N;
        Sleep(50);
    }
    return 0;
}

这样我们在每次运行都会得到理想的值了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Or8v2Qgy-1689425356252)(C++.assets/image-20230706050549533.png)]

关键段(Critical_Section,也叫临界区)

回顾单例出现的问题

我们还记得在学习 设计模式中的单例模式时,提到过一个问题,那就是在多线程下,可能会创建出来多个对象

我们试着把这个问题显现出来

单例:

class CSingleton {

    CSingleton(): m_a(0){}
    CSingleton(const CSingleton&) = delete;  //弃用 拷贝构造函数
    ~CSingleton(){}
    static CSingleton* m_psin;

    static class Destory {
    public:
        ~Destory() {
            if (m_psin) {
                delete m_psin;
            }
            m_psin = nullptr;
        }
    } m_destory;

public:
    //问题:在多线程下 可能会创建出来多个对象
    static CSingleton* GetSingleton() {
        //1.加锁
        if (!m_psin)
            m_psin = new CSingleton;
        //2.解锁
        return m_psin;
    }
    static void DestorySingleton(CSingleton*& psin) {
        if (m_psin) {
            delete m_psin;
        }
        psin = m_psin = nullptr;
    }

    int m_a;
};
CSingleton* CSingleton::m_psin = nullptr;

CSingleton::Destory CSingleton::m_destory;//类外定义

主函数

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    for(int i=0;i<30;i++){
        CreateThread(nullptr/*默认的安全属性*/,
                           0/*windows 默认1M*/,
                           &ThreadProc/*线程函数的地址*/,
                           nullptr/*线程函数的参数*/,
                           0/*0:立即运行 ,SUS_PEND 挂起*/,
                           nullptr/*线程ID*/);
    }
    return a.exec();
}

线程函数

DWORD WINAPI ThreadProc (LPVOID lpThreadParameter){
    Sleep(100);
    CSingleton* pSin = CSingleton::GetSingleton();
    qDebug()<<pSin;

    return 0;
}

运行后通过查看对象地址可以发现,真的可能会创建出多个对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GdKfrcet-1689425356252)(C++.assets/image-20230706052920751.png)]

那么这个问题就不能用原子访问来解决了,我们需要使用到关键段

关键段基本使用

结构体:CRITICAL_SECTION和四个函数(初始化、进入、离开、删除)

我们可以使用这个来解决单例中出现的问题

CRITICAL_SECTION m_cs;  //关键段的变量
    static CSingleton* GetSingleton() {
        //1.加锁
        if (!m_psin){
            ::EnterCriticalSection(&m_cs);
            if (!m_psin)
            m_psin = new CSingleton;
            //2.解锁
            ::LeaveCriticalSection(&m_cs);
        }
        return m_psin;
    }

主函数中初始化和删除,注意删除之前最好要加个延迟

    ::InitializeCriticalSection(&m_cs);  //初始化关键段

	......
        
    Sleep(3000);
    ::DeleteCriticalSection(&m_cs);  //删除关键段

这样在运行过程中就只会创建一个对象了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4MyuC6S9-1689425356253)(C++.assets/image-20230706055339117.png)]

运行原理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XdASuiAx-1689425356253)(C++.assets/image-20230706055416621.png)]

封装关键段

把创建对象和四个结构体都封装到一个类中

class MyLock{
    CRITICAL_SECTION m_cs;  //关键段的变量
public:
    MyLock(){
        ::InitializeCriticalSection(&m_cs);  //初始化关键段
    }
    ~MyLock(){
        ::DeleteCriticalSection(&m_cs);  //删除关键段
    }
    void Lock(){
        ::EnterCriticalSection(&m_cs);
    }
    void UnLock(){
        ::LeaveCriticalSection(&m_cs);
    }
};

使用的时候直接创建类对象调用函数即可

static CSingleton* GetSingleton() {

    static MyLock lock;
    //1.加锁
    if (!m_psin){
        lock.Lock();
        if (!m_psin)
            m_psin = new CSingleton;
        //2.解锁
        lock.UnLock();
    }
    return m_psin;
}

Qt下的多线程

多线程与进度条

我们创建一个带有设计界面的项目,并且在设计界面中放入一个进度条以及四个按钮组件,组件的作用分别是启动、暂停、恢复和退出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kZA18Ere-1689425356254)(C++.assets/image-20230707050229997.png)]

并将这些按钮直接转到clicked的槽函数

private slots:
    void on_pb_start_clicked();

    void on_pb_pause_clicked();

    void on_pb_resume_clicked();

    void on_pb_quit_clicked();
void MainWindow::on_pb_start_clicked()
{

}

void MainWindow::on_pb_pause_clicked()
{

}

void MainWindow::on_pb_resume_clicked()
{

}

void MainWindow::on_pb_quit_clicked()
{

}

在启动的槽函数中实现创建线程

void MainWindow::on_pb_start_clicked()
{

    if(!m_handle){
        m_isQuit = false;
        m_handle = CreateThread(nullptr/*默认的安全属性*/,
                           0/*windows 默认1M*/,
                           &ThreadProc/*线程函数的地址*/,
                           (void*)this/*线程函数的参数*/,
                           0/*0:立即运行 ,SUS_PEND 挂起*/,
                           nullptr/*线程ID*/);
    }

}

线程函数,在线程函数中实现设置进度条的值

DWORD WINAPI ThreadProc (LPVOID lpThreadParameter){

    MainWindow* pMain = (MainWindow*)lpThreadParameter;
    int n = 0;

    while(!pMain->m_isQuit){
        pMain->GetUI()->progressBar->setValue(n);  //设置进度条的值

        n = ++n%101;
        Sleep(200);
    }
    return 0;
}

在头文件中设置公有的ui接口以及退出标志和线程句柄

public:
    Ui::MainWindow * GetUI(){return ui;}

    bool m_isQuit;  //是否退出
    HANDLE m_handle;

在构造函数中初始化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4A6jruyn-1689425356254)(C++.assets/image-20230707050654263.png)]

到这里我们先点击启动按钮进行测试,发现程序 会崩溃

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I3Akl0z2-1689425356254)(C++.assets/image-20230707051136480.png)]

报错原因是:在另一个线程中设置了当前组件的值时,使用sendEvent发送事件,跨线程报错。

针对于此情况解决方法就是手动设置信号与槽进行连接

所以我们先创建一个自定义槽函数和信号

private slots:
    void slots_setValue(int);

signals:
    void signals_sendValue(int);

这样我们就在线程函数中去发射信号,然后在槽函数中接收信号并且设置进度条的值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dGmmFxFW-1689425356255)(C++.assets/image-20230707053100143.png)]

槽函数

void MainWindow::slots_setValue(int n){
    ui->progressBar->setValue(n);  //设置进度条的值
}

之后对信号和槽进行连接

    connect(this,SIGNAL(signals_sendValue(int)),this,SLOT(slots_setValue(int)),Qt::QueuedConnection);

注意这个函数有有一个第五个参数,用来设置连接类型,我们之前报错只要就是因为默认的连接类型不符合,我们可以选择AutoConnection或QueuedConnection。

这样程序就可以正常运行了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SjiGfc1R-1689425356255)(C++.assets/image-20230707053351443.png)]

然后我们再来实现以下退出槽函数

void MainWindow::on_pb_quit_clicked()
{
    m_isQuit = true;  //通知线程退出

    if( WAIT_OBJECT_0 == WaitForSingleObject(m_handle,3000)){
        qDebug()<<"子线程已经退出";
        if(m_handle){
            CloseHandle(m_handle);
            m_handle = nullptr;
        }
    }
}

暂停和恢复就是将线程挂起和连接

暂停

void MainWindow::on_pb_pause_clicked()
{
    ::SuspendThread(m_handle);
    qDebug()<<"子线程已经挂起";
}

恢复

void MainWindow::on_pb_resume_clicked()
{
    ::ResumeThread(m_handle);
    qDebug()<<"子线程已经恢复";

}

通过测试都可以正常运行

Qt-QThread

在Qt中封装了创建线程的类:QThread,一般情况下,手动添加一个类继承QThread,这样既能继承QThread的功能,又能扩展自己的功能。

添加新文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fzZ3zazI-1689425356255)(C++.assets/image-20230711205938295.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EsNZMdwJ-1689425356255)(C++.assets/image-20230711210119334.png)]

Qt线程+关键段:为何要使用关键段,原来的只是靠一个暂停标志位,while一直在空跑,也会耗费一点cpu。好一点的效果是让其等待,直到恢复。

所以我们就会创建一个新的线程用来阻塞当前线程

头文件

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>
#include <windows.h>

class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = 0);
    ~MyThread();

signals:
    void signals_sendThreadValue(int);  //声明信号

public slots:

public:
    virtual void run();

    bool m_isQuit;  //标识当前线程,是否退出
    bool m_isPause;  //是否暂停

    CRITICAL_SECTION m_cs;
};

#endif // MYTHREAD_H

源文件

#include "mythread.h"
#include <QDebug>

MyThread::MyThread(QObject *parent) : QThread(parent),m_isQuit(false),m_isPause(false)
{
    ::InitializeCriticalSection(&m_cs);
}
MyThread::~MyThread(){
    ::DeleteCriticalSection(&m_cs);

}

void MyThread::run(){
    int n = 0;
    while(!m_isQuit){

        if(m_isPause){
//            qDebug()<<"暂停了";
//            continue;
            qDebug()<<"暂停了";
            ::EnterCriticalSection(&m_cs);
            qDebug()<<"进入关键段";
            ::LeaveCriticalSection(&m_cs);
            qDebug()<<"离开关键段";

        }

        //发射一个信号
        emit signals_sendThreadValue(n);

        n = ++n%101;
        Sleep(100);
    }
    qDebug()<<"子线程即将退出";
}

信号和槽连接以及实现线程功能

    connect(&this->m_thread,SIGNAL(signals_sendThreadValue(int)),this,SLOT(slots_setValue(int)),Qt::QueuedConnection);

启动线程

void MainWindow::on_pb_start_clicked()
{
    m_thread.m_isQuit = false;
    m_thread.start();  //启动线程
}

注意:启动线程需要调用系统函数start()而不是直接调用run()。

暂停线程

void MainWindow::on_pb_pause_clicked()
{
    ::EnterCriticalSection(&m_thread.m_cs);
    m_thread.m_isPause = true;
}

恢复线程

void MainWindow::on_pb_resume_clicked()
{
    m_thread.m_isPause = false;
    ::LeaveCriticalSection(&m_thread.m_cs);
}

退出线程

void MainWindow::on_pb_quit_clicked()
{
    m_thread.m_isQuit = true;
}

运行测试后也没有什么毛病

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U6mPdhBq-1689425356255)(C++.assets/image-20230711214629735.png)]

到此,线程相关的知识就结束了

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

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

相关文章

【Tauri + React 实战】VCluster - 了解技术选型与开发环境配置

VCluster A React Tauri App as visualizer of apps cluster on windows. 背景介绍 VCluster是一个在开发环境下&#xff0c;用以对一系列应用集群&#xff08;如分布式、微服务&#xff09;进行可视化管理的桌面应用程序&#xff0c;目标是实现类似 docker-compose 那样的集…

深入学习Python自定义函数

目录 0. 前言1. 最基础的自定义函数写法2. 参数传递3. 函数返回值4. 作用域5. 匿名函数6. 装饰器7. 闭包8. 生成器9. 为自定义函数写注解 0. 前言 今天来深入学习 Python 中的自定义函数&#xff0c;为的是日后能够写出时间和内存更好的优化&#xff0c;以及形成良好的编程习惯…

原型链:揭开JavaScript背后的神秘面纱

文章目录 1. 对象2. 原型&#xff08;prototype&#xff09;3. 原型链&#xff08;prototype chain&#xff09;4. 构造函数&#xff08;constructor&#xff09;5. prototype 属性6. 实例&#xff08;instance&#xff09;7. 原型继承&#xff08;prototype inheritance&#…

HFSS仿真微带型威尔金森功分器学习笔记

HFSS仿真微带型威尔金森功分器 文章目录 HFSS仿真微带型威尔金森功分器1、 求解器设置2、 建模3、 边界条件设置4、 激励方式设置5、 扫频设置6、 设计检查&#xff0c;仿真分析7、 数据后处理 设计要求&#xff1a; 频带范围0.9~1.1GHz输入端口的回波损耗&#xff1e;20dB频带…

STL算法篇之拷贝修改类算法

STL算法篇之拷贝修改类算法 拷贝类算法copy与copy_backwardremove与remove_copyremove_if与remove_copy_if 修改类算法replace与replace_copyreplace_if与replace_copy_ifiter_swap与swap与swap_range、unique与unique_copy 拷贝类算法 1.copy 区间拷贝 2.copy_backward 逆向拷…

Redis双写一致性?

双写一致性&#xff1a;当修改了数据库的数据也要同时更新缓存的数据&#xff0c;缓存和数据库的数据要保持一致。 Redis作为缓存&#xff0c;mysql的数据如何与redis进行同步呢&#xff1f;&#xff08;双写一致性&#xff09; 1.我们当时做排行榜业务时&#xff0c;把历史榜…

CentOS7中安装docker并配置阿里云加速器

文章目录 一、docker的安装二、docker的卸载三、配置加速器四、docker-compose安装五、docker-compose卸载六、docker-compose相关命令七、常用shell组合 一、docker的安装 参考&#xff1a;https://docs.docker.com/engine/install/centos 本文内容是基于&#xff1a;CentOS L…

Python 算法基础篇:大O符号表示法和常见时间复杂度分析

Python 算法基础篇&#xff1a;大 O 符号表示法和常见时间复杂度分析 引言 1. 大 O 符号表示法 a ) 大 O 符号的定义 b ) 示例代码 2. 常见时间复杂度分析总结 引言 在分析和比较算法的性能时&#xff0c;时间复杂度是一项重要的指标。而大 O 符号表示法是用来描述算法时间复杂…

Redis 从入门到精通【进阶篇】之Lua脚本详解

文章目录 0. 前言1. Redis Lua脚本简介1.1 Lua脚本介绍Lua语言概述&#xff1a;Lua脚本的特点&#xff1a; 1.2 Redis中为何选择LuaLua与Redis的结合优势Lua脚本在Redis中的应用场景 2. Redis Lua脚本的执行流程1. 加载脚本&#xff1a;1.1 脚本缓存机制&#xff1a;1.2 脚本加…

C++ 可变参数函数用法与template模板泛型编程

目录 1、可变参数函数 &#xff08;1&#xff09;定义 &#xff08;2&#xff09;常用使用场景 2、template模板用法 1、可变参数函数 &#xff08;1&#xff09;定义 可变参数函数的可变参数一般使用省略号表示&#xff0c;如下&#xff1a; void func(int a,...);{} &…

牛P!安卓渗透神器PhoneSploit-Pro

工具介绍 一种集成的黑客工具&#xff0c;可使用ADB(Android Debug Bridge) 和Metasploit-Framework完成自动化&#xff0c;一键获取 Meterpreter 会话。 关注【Hack分享吧】公众号&#xff0c;回复关键字【230524】获取下载链接 如果设备打开了 ADB 端口&#xff0c;该工具可…

关于Context和ContextImpl还有ContextWrapper的关系

关于Context和ContextImpl还有ContextWrapper的关系 1.Context和ContextImpl还有ContextWrapper的关系 ​ 图一.Context和ContextImpl还有ContextWrapper的关系示意图 1.1.ContextImpl是Context的实现类 从Context和ContextImpl的源代码中,可以看出Context是一个抽象类,具体…

pytorch+CRNN实现

最近接触了一个仪表盘识别的项目&#xff0c;简单调研以后发现可以用CRNN来做。但是手边缺少仪表盘数据集&#xff0c;就先用ICDAR2013试了一下。 结果遇到了一系列坑。为了不使读者和自己在以后的日子继续遭罪。我把正确的代码发到下面了。 1&#xff09;超参数请不要调整&am…

实体店搭建多用户商城系统有什么好处

现在很多的线下店铺都开始慢慢的转型线上了&#xff0c;想线上线下相结合&#xff0c;但是最近很多的商家都在问什么样的B2B2C商城系统开发适合线下店铺呢?这个问题今天加速度jsudo小编给大家一起整理如下&#xff0c;相信商家看完后就知道如何选择一款合适的商城系统了。 一、…

Spring Batch之读数据—读XML文件(三十二)

一、XML格式文件解析 XML是一种通用的数据交换格式&#xff0c;它的平台无关性、语言无关性、系统无关性&#xff0c;给数据集成与交换带来了极大的方便。XML在Java领域的解析方式有两种&#xff0c;一种叫SAX&#xff0c;另一种叫DOM。SAX是基于事件流的解析&#xff0c;DOM是…

刷题总结1

暑假第二周练习题 - Virtual Judge (vjudge.net) 该题就是将含4的数字全部跳过&#xff0c;不难发现&#xff0c;这就导致每位数都要少一个树&#xff0c;这就和9进制十分像&#xff0c;我们只要将该数字转化为9进制&#xff0c;然后将该9进制树的每位大于等于4的树加一就行了&…

【CXL】CXL QoS Telemetry 介绍

&#x1f525;点击查看精选 CXL 系列文章&#x1f525; &#x1f525;点击进入【芯片设计验证】社区&#xff0c;查看更多精彩内容&#x1f525; &#x1f4e2; 声明&#xff1a; &#x1f96d; 作者主页&#xff1a;【MangoPapa的CSDN主页】。⚠️ 本文首发于CSDN&#xff0c…

51单片机的智能交通控制系统【含仿真+程序+演示视频带原理讲解】

51单片机的智能交通控制系统【含仿真程序演示视频带原理讲解】 1、系统概述2、核心功能3、仿真运行及功能演示4、程序代码 1、系统概述 该系统由AT89C51单片机、LED灯组、数码管组成。通过Protues对十字路口红绿灯控制逻辑进行了仿真。 每个路口包含了左转、右转、直行三条车道…

rapid_latex_ocr: 更快更好用的公式图像转latex工具

Rapid Latex OCR rapid_latex_ocr是一个将公式图像转为latex格式的工具。仓库中的推理代码来自修改自LaTeX-OCR&#xff0c;模型已经全部转为ONNX格式&#xff0c;并对推理代码做了精简&#xff0c;推理速度更快&#xff0c;更容易部署。仓库只有基于ONNXRuntime或者OpenVINO推…

AI辅助瞄准系统开发与实战(一)

文章目录 前言系统窗体设计提示弹窗功能主体页面 windows窗体绘制矩形绘制自定义线程池完整代码 总结 前言 直接看效果&#xff0c;狗头&#xff1a; 之所以搞这个的话&#xff0c;当然主要一方面是因为确实有点意思在里面&#xff0c;此外在很久以前&#xff0c;也有很多的UP…