Qt多线程基础(一)线程同步之互斥锁同步

2023-11-12

一、直接使用QMutex进行同步

创建线程方法:继承自QThread,重写void run()函数,调用成员start()启动线程,start()中可加入优先级参数。

互斥锁同步方法:void run()函数中使用QMutex来实现同步,当多个线程访问共享变量时,应使用lock/trylock和unlock将对共享变量的操作代码包裹,以保证同步访问共享变量。(C++中引起线程安全的共享资源只有两种:全局变量和静态变量)

示例代码中两个Thread均继承自QThread(),为了保证互斥锁对两个线程均可见,QMutex在一个线程CPP文件中定义,另一个线程文件做extern声明。

示例代码如下:

thread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QtCore>
#include <QMutex>

class MyThread:public QThread
{
public:
    MyThread(QString name);
    void run();
private:
    QString mName;

};

#endif // MYTHREAD_H

thread.cpp

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

int i=50;
QMutex mutex;
MyThread::MyThread(QString name):QThread(),mName(name)
{
    qDebug()<<"creating.."<<endl;
}

void MyThread::run()
{
    qDebug()<<this->mName<<"running.."<<endl;
    mutex.lock();
    /*
    for(;i<100;i++)
    {
        qDebug()<<this->mName<<i<<endl;

    }
    */
    i++;
    i*=2;
    qDebug()<<this->mName<<i<<endl;
    mutex.unlock();

    qDebug()<<this->mName<<"stop running.."<<endl;
    sleep(1);

}

thread2.h

#ifndef MYTHREAD2_H
#define MYTHREAD2_H
#include <QThread>
#include <QMutex>
class MyThread2:public QThread
{
public:
    MyThread2(QString name);
    void run();
private:
    QString mName;

};

#endif // MYTHREAD2_H

thread2.cpp

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

extern int i;
extern QMutex mutex;
MyThread2::MyThread2(QString name):QThread(),mName(name)
{
 qDebug()<<"creating.."<<endl;
}

void MyThread2::run()
{
    qDebug()<<this->mName<<"running.."<<endl;
    mutex.lock();
    /*
    for(;i>0;i--)
    {
        qDebug()<<this->mName<<i<<endl;
    }
    */
    i--;
    i/=2;
    qDebug()<<this->mName<<i<<endl;
    mutex.unlock();
    qDebug()<<this->mName<<"stop runnning.."<<endl;
    sleep(1);
}

main.cpp

#include <QCoreApplication>
#include "mythread.h"
#include "mythread2.h"

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

    MyThread2 thread2("thread2");

    thread1.start(QThread::HighestPriority);//高优先级的任务获得更多的CPU使用比,因此先计算完成
    thread2.start();//相对低优先级的任务会后完成

    thread1.wait();
    qDebug()<<"thread1 is done!"<<endl;
    thread1.wait();
    qDebug()<<"thread2 is done!"<<endl;

//    thread1.exit();
     return a.exec();
}

实验结果:



二、使用互斥锁类QMutexLocker(浅谈RAII)


问题:使用QMutex的上锁、解锁操作直接同步会有一个致命缺陷:当代码提前退出时(如函数中多处return或C++抛出异常),可能并未执行unlock(),若其他线程采用lock()阻塞式上锁会一直被阻塞等待释放,导致资源泄露。

解决:根据RAII的思想,我们应该尽量使用对象管理资源,构造时获取互斥锁,析构时释放锁。(参见Effective C++条款13)

具体来讲,QMutexLocker作为一个便利类,可以解决以下两种函数有多个出口的情况:

(1)第一种情况是函数内部多次return,如果直接使用QMutex上锁,必须保证每个return之前都及时释放锁资源(每个return前都要加上unlock()),否则当前线程的run()退出时另一个线程的run()无法获取锁,造成死锁。如下例所示:

  int complexFunction(int flag)
  {
      QMutexLocker locker(&mutex);

      int retVal = 0;

      switch (flag) {
      case 0:
      case 1:
          return moreComplexFunction(flag);
      case 2:
          {
              int status = anotherFunction();
              if (status < 0)
                  return -2;
              retVal = status + flag;
          }
          break;
      default:
          if (flag > 10)
              return -1;
          break;
      }

      return retVal;
  }
可以看到如果使用QMutex进行上锁,在线程的run()函数中调用该函数,一旦该函数在中途return,又没有及时调用unlock()就会导致互斥锁永远没有机会释放。除非在每一个return前加上QMutex的unlock()。

如果我们按上述代码所示,使用QMutexLocker管理QMutex,由于函数中的QMutexLocker是一个局部对象,因此return的时候一定会调用析构并在析构内部完成互斥锁的释放。

(2)另一种情况是C++抛出异常的情况:C++标准里明确规定抛出异常时仍能保证局部对象的析构调用,这也是RAII技术的保证。也就是说由于QMutexLocker是局部对象,所以一旦遇到函数退出时,局部对象被释放都会调用析构,析构内部会释放锁。(参见Effective C++条款29)

至于为何C++抛出异常时仍能保证释放局部对象(栈上变量),这是C++标准规定,请参看:

https://segmentfault.com/q/1010000002498987


因此,Qt提供了互斥锁类QMutexLocker,当QMutexLocker作为局部对象时,函数中途return或抛出异常时均会调用析构释放对象,而该类的析构函数内部调用了参数绑定的QMutex对应的unlock()函数,这也是RAII技术的基础保证。空口无凭,如图为证,这是QMutexLocker内部的析构函数实现:


可以看到,析构里面调用unlock()函数,而unlock()函数内部调用mutex()->unlock(),mutex()是一个常量函数,返回QMutexLocker绑定的QMutex,因此mutex()->unlock()调用了QMutex的unlock()函数,完成了对互斥锁的解锁。如图:

可以看到上方的QMutex类中将QMutexLocker声明成了QMutex的友元!因此QMutexLocker可以在析构中调用QMutex的unlock()完成锁资源的释放。这就使得当run()函数有多个出口退出时(多处return或抛出异常),析构被调用并及时完成互斥锁的释放,从而避免锁资源的泄露问题。

另外一个用到RAII思想的技术比如C++STL的智能指针,也是为了避免堆上空间未及时释放的情况。如果使用普通指针申请堆空间,函数中途抛出异常(比如另一个指针申请空间失败,抛出bad_alloc异常),那该指针申请的空间将无法释放,有人说使用捕获异常在catch中释放所有资源,比如此处泄露的内存,但这并不是个好办法,于是根据RAII思想,智能指针产生了,当智能指针的引用计数减为0时会释放这块内存(delete)。

看到这里,终于放心了吗?这是为什么Qt也推荐使用QMutexLocker的原因:RAII技术可以让我们写出异常安全的代码





本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Qt多线程基础(一)线程同步之互斥锁同步 的相关文章

  • 金特 + XNA (C#)

    是否可以使用jint http jint codeplex com操作使用 XNA C 创建的 3D 环境 并向该环境添加功能 再次使用 jint 作为 Jint 的贡献者 我会推荐你Jint http jint codeplex com
  • 我应该把 try/catch 和“using”语句放在哪里? [复制]

    这个问题在这里已经有答案了 可能的重复 try catch using 正确的语法 https stackoverflow com questions 4590490 try catch using right syntax 我想try c
  • 编写此代码片段的有效方法是什么? [关闭]

    Closed 这个问题需要细节或清晰度 help closed questions 目前不接受答案 更有效和 或更短地重写此代码以节省字节并显得不那么冗长的方法 if N 2 0 N 6 N 8 N 10 N 12 N 14 N 16 N
  • JSON.Net 反序列化返回“null”

    我正在使用 JSON Net 反序列化 JSON 字符串 JSON 字符串是 string testJson Fruits Apple color red size round Orange Pro
  • 锁定 ASP.NET 应用程序变量

    我在 ASP NET 应用程序中使用第三方 Web 服务 对第 3 方 Web 服务的调用必须同步 但 ASP NET 显然是多线程的 并且可能会发出多个页面请求 从而导致对第 3 方 Web 服务的同时调用 对 Web 服务的调用封装在自
  • XPATH 查询、HtmlAgilityPack 和提取文本

    我一直在尝试从名为 tim new 的类中提取链接 我也得到了解决方案 给出了解决方案 片段和必要的信息here https stackoverflow com questions 2982862 extracting a table ro
  • 将 OpenCV Mat 转换为数组(可能是 NSArray)

    我的 C C 技能很生疏 OpenCV 的文档也相当晦涩难懂 有没有办法获得cv Mat data属性转换为数组 NSArray 我想将其序列化为 JSON 我知道我可以使用 FileStorage 实用程序转换为 YAML XML 但这不
  • C++ 将联合强制转换为其成员类型之一

    以下对我来说似乎完全符合逻辑 但不是有效的 C 联合不能隐式转换为其成员类型之一 有人知道为什么不这样做的充分理由吗 union u int i char c function f int i int main u v v i 6 f v
  • 在 C++11 中移出 stdpriority_queue 的元素

    最小的工作示例 include
  • 无法为 wsdl 文件创建服务引用

    I have wsdl文件和xsd我本地机器上的文件 我想在项目中添加服务引用 我没有网络服务 我只有wsdl file 我收到以下错误 The document was understood but it could not be pro
  • 如何使用 CSI.exe 脚本参数

    当你运行csi exe 安装了 Visual Studio 2015 update 2 您将得到以下语法 Microsoft R Visual C Interactive Compiler version 1 2 0 51106 Copyr
  • EnumDisplayDevices 与 WMI Win32_DesktopMonitor,如何检测活动监视器?

    对于我当前的 C 项目 我需要为在大量计算机上连接并处于活动状态的每个监视器检测一个唯一的字符串 研究指出了两种选择 使用 WMI 并查询 Win32 DesktopMonitor 以获取所有活动监视器 使用 PNPDeviceID 来唯一
  • 浮点字节序?

    我正在为实时海上模拟器编写客户端和服务器 并且由于我必须通过套接字发送大量数据 因此我使用二进制数据来最大化可以发送的数据量 我已经了解整数字节顺序以及如何使用htonl and ntohl为了规避字节顺序问题 但我的应用程序与几乎所有模拟
  • 从 NumPy 数组到 Mat 的 C++ 转换 (OpenCV)

    我正在围绕 ArUco 增强现实库 基于 OpenCV 编写一个薄包装器 我试图构建的界面非常简单 Python 将图像传递给 C 代码 C 代码检测标记并将其位置和其他信息作为字典元组返回给 Python 但是 我不知道如何在 Pytho
  • 如何在dll级别读取app.config? [复制]

    这个问题在这里已经有答案了 我在一个解决方案中有一个控制台应用程序项目和库项目 dll The 图书馆项目有 app config 文件 我在其中存储我在库中使用的一些键值对 控制台应用程序引用此 dll 我有另一个 app config
  • Autoconf 问题:“错误:C 编译器无法创建可执行文件”

    我正在尝试使用 GNU 自动工具构建一个用 C 编写的程序 但显然我设置错误 因为当configure运行 它吐出 configure error C compiler cannot create executables 如果我看进去con
  • Linq.Select() 中的嵌套表达式方法调用

    I use Select i gt new T 每次手动点击数据库后将我的实体对象转换为 DTO 对象 以下是一些示例实体和 DTOS 用户实体 public partial class User public int Id get set
  • 有没有办法直接在函数参数中格式化字符串而不是使用临时字符串?

    我有一个接受字符串 字符数组 作为参数的函数 void enterString char my string 当使用这个函数时 我经常发现自己想要输入格式化的字符串 我使用 sprintf 来做到这一点 然而 我每次都必须创建一个临时字符串
  • “必须声明标量变量”错误[重复]

    这个问题在这里已经有答案了 必须声明标量变量 Id SqlConnection con new SqlConnection connectionstring con Open SqlCommand cmd new SqlCommand cm
  • 将 char 绑定到枚举类型

    我有一段与此非常相似的代码 class someclass public enum Section START MID END vector section Full void ex for int i 0 i section

随机推荐

  • ST-LINK/V2驱动下载与安装

    1 下载 ST LINKV2仿真器 正点原子资料下载中心 1 0 0 文档 openedv com 点击上面链接到正点原子就可以下载 如下图所示 2 安装 1 将ST LINK连接到电脑 在设备管理器中可以看到是没有通用串行设备 说明电脑没
  • Arthas使用方法

    1 简介 Arthas 是Alibaba开源的Java诊断工具 安装在系统所在服务器 可以帮助开发人员或者运维人员查找问题 分析性能 bug追踪 下载路径 https arthas aliyun com arthas boot jar 2
  • 功能视图的组成

    功能视图是构建联邦学习系统所需功能的技术中立的视图 功能视图描述了支持联邦学习活动所必需功能的分布 定义了功能之间的依赖关系 功能视图涵盖的联邦学习内容如图 包括 功能组件 功能层 跨层功能 功能组件是参与某一活动所需的 能实现的功能构件
  • 与OpenAI的30 天

    30 天 我计划在 GitHub 上测试开源 Ai 项目 并学习如何自己构建一个项目 这些帖子会很短 重点是了解您可以使用 Ai 创造什么以及需要准备什么 图像是使用midjourney生成的 我一直在收集由像你我这样的开发人员创建的 30
  • react-从0到1新建react项目

    目录 1 脚手架创建项目 2 分析目录 3 动态写入值 编辑 4 引入组件 编辑 5 组件传值 6 控制组件传值的类型 类型校验 7 组件插槽 8 函数组件和类组件 9 添加事件 10 引入state 在页面上响应式改变值 编辑 11 子组
  • 【C语言】C语言的495个问题

    文章目录 1 声明和初始化 基本类型 1 1 各类型区别 1 2 为什么不精确定义标准类型的大小 1 3 因为C没有精确定义标准类型大小 那么用typedef定义int16和int32是否能解决问题呢 1 4 新64位机上64位类型是什么样
  • android下m、mm、mmm编译命令的使用

    通过查看android源码目录下的build envsetup sh文件 可知 m Makes from the top of the tree mm Builds all of the modules in the current dir
  • C#编程基础(万字详解,这一篇就够了)

    C 及其开发环境简介 C 概述 C 的编程功能 C 与 Net的关系 Net C C 的集成开发环境 Windows上编写C 程序 Linux Mac OS上编写C 程序 运行第一个HelloWorld程序 C 基本语法 程序实例 C 基本
  • java mail 添加附件以及邮件中穿插图片方法

    部分代码 创建邮件中的附件 param filepath 附件的路径 return 生成附件的对象 throws Exception 测试 filepath e 测试 tomcat png e盘下的tomcat图片 public stati
  • 基本的垃圾回收算法总结

    Java虚拟机中的垃圾回收机制的是虚拟机的灵魂所在 下面介绍下虚拟机中的主要回收算法 引用计数法 实现 对于实例化的一个对象O 只要有任何一个其他的对象引用O O的引用计数器就加一 当引用失效的情况下 引用计数器减一 优点 实现简单 缺点
  • 全国各大城市的经纬度表,留着以后做查询库用

    安徽省合肥的经纬度北纬31 52东经117 17 安徽省安庆的经纬度北纬30 31东经117 02 安徽省蚌埠的经纬度北纬32 56东经117 21 安徽省亳州的经纬度北纬33 52东经115 47 安徽省巢湖的经纬度北纬31 36东经11
  • css 卡片翻转效果实现

    直接贴代码
  • Vue 引入高德地图 vue-amap

    一 在高德开发平台 获取Key 已有可跳过 高德开发者平台 链接地址 1 控制台 我的应用 创建应用 添加key 创建应用 新建应用 选择web端 JS平台 last 到这里的 key 就有了 还得到了一个安全密钥 二 引入vue amap
  • 5G应用标志着移动互联网新时代真正到来

    5G网络普及后很可能实现已经多次被我们所畅想的万物互联 通过网络把家用电视 冰箱 洗衣机 空调等连接起来 再通过一个软件来进行控制 5G超高的网速 超低的延迟将会使无人驾驶 无人机作业 远程医疗技术等步入成熟阶段 因此 5G的应用标志着移动
  • TypeScript 在 vue 中的使用

    本文主要介绍 TypeScript 在 vue 中的使用 还有一些j注释起来的 js 代码做对照 参考链接 合成 API 的 TypeScript vue3中配合使用TS 还需要额外安装一个vscode插件 Typescript Vue P
  • 基于snooplog分析蓝牙连接过程,进一步学习蓝牙协议栈

    一 什么是BT snoop log 首先问题 1 为什么远端发来的消息没有收到 2 为什么搜索不到设备 3 为什么连不上 4 总之 研发过程中会遇到很多奇奇怪怪的问题 我们无法通过现象去分析原因 也不可能再过一遍代码吧 这时候需要再某些代码
  • 期货交易,一些你不得不知道的技巧

    众所周知 期货市场变化莫测 既有风险 又兼具了高回报 因此也被誉为 21世纪冒险者的游戏 虽然期货投资受市场 政治形势 经济发展等因素的影响 但作为一个成熟的交易模式 期货依然有着很大的魅力 既可以做长线 也可以短线为王 但无论采用何种交易
  • 如何建设水利数字孪生流域

    数字孪生流域是以物理流域为单元 时空数据为底座 数学模型为核心 水利知识为驱动 对物理流域全要素和水利治理管理活动全过程的数字化映射 智能化模拟 实现与物理流域同步仿真运行 虚实交互 迭代优化 建设水利数字孪生流域 重点包括以下几件事 获取
  • nginx日志access.log error.log按天生成存储,定时删除日志

    bin bash function cut nginx log files for lnmp v0 5 and v0 6 author http lnmp org set the path to nginx log files log fi
  • Qt多线程基础(一)线程同步之互斥锁同步

    一 直接使用QMutex进行同步 创建线程方法 继承自QThread 重写void run 函数 调用成员start 启动线程 start 中可加入优先级参数 互斥锁同步方法 void run 函数中使用QMutex来实现同步 当多个线程访