Qt/C++ 多线程同步机制详解及应用

news2024/9/24 17:14:20

在多线程编程中,线程之间共享资源可能会导致数据竞争和不一致的问题。因此,采用同步机制确保线程安全至关重要。在Qt/C++中,常见的同步机制有:互斥锁(QMutexstd::mutex)、信号量(QSemaphore)、读写锁(QReadWriteLock)、原子操作(QAtomicInt 等)以及条件变量(QWaitConditionstd::condition_variable)。本文将详细介绍这些常用同步机制,并通过代码示例帮助理解其使用场景。

在这里插入图片描述

1. 互斥锁(QMutex / std::mutex

互斥锁是最常用的线程同步机制之一,它可以保证在同一时刻只有一个线程进入临界区。其基本原理是通过锁定和解锁互斥锁来确保共享资源的独占访问。

示例代码及注释
#include <QMutex>
#include <QThread>
#include <iostream>

// 声明一个全局互斥锁,用于保护共享资源
QMutex mutex;
int sharedResource = 0;  // 共享资源

class Worker : public QThread {
public:
    void run() override {
        mutex.lock();  // 锁定互斥锁,确保当前线程独占资源
        std::cout << "Thread " << QThread::currentThreadId() << " is entering the critical section." << std::endl;
        sharedResource++;  // 修改共享资源
        std::cout << "Shared Resource: " << sharedResource << std::endl;
        mutex.unlock();  // 释放互斥锁,其他线程可以进入
    }
};

int main() {
    Worker worker1, worker2;  // 创建两个工作线程
    worker1.start();  // 启动第一个线程
    worker2.start();  // 启动第二个线程
    worker1.wait();   // 等待第一个线程完成
    worker2.wait();   // 等待第二个线程完成
    return 0;
}
解释:
  1. 互斥锁机制:使用 mutex.lock() 锁定,mutex.unlock() 解锁,保证共享资源在某个线程执行完后才可被其他线程访问。
  2. 线程独占性:多个线程同时访问共享资源时,只有获取到锁的线程能进入临界区,避免竞争条件。

2. 信号量(QSemaphore

信号量是一种同步机制,用于控制多个线程同时访问有限数量的资源。与互斥锁不同,信号量可以允许多个线程并发访问,直到信号量资源用尽。

示例代码及注释
#include <QSemaphore>
#include <QThread>
#include <iostream>

// 初始化信号量,允许最多3个线程同时进入临界区
QSemaphore semaphore(3);

class Worker : public QThread {
public:
    void run() override {
        semaphore.acquire();  // 获取信号量
        std::cout << "Thread " << QThread::currentThreadId() << " is entering." << std::endl;
        QThread::sleep(1);    // 模拟执行任务的时间
        std::cout << "Thread " << QThread::currentThreadId() << " is leaving." << std::endl;
        semaphore.release();  // 释放信号量
    }
};

int main() {
    Worker worker1, worker2, worker3, worker4;  // 创建四个线程
    worker1.start();  // 启动线程
    worker2.start();
    worker3.start();
    worker4.start();  // 第四个线程需等待其他线程释放信号量
    worker1.wait();
    worker2.wait();
    worker3.wait();
    worker4.wait();
    return 0;
}
解释:
  1. 并发控制:信号量通过 acquire() 获取资源,release() 释放资源,控制多个线程并发执行。
  2. 资源计数:当信号量值为零时,其他线程必须等待信号量被释放才能继续执行。

3. 读写锁(QReadWriteLock

读写锁允许多个线程同时读取共享资源,但只有一个线程可以进行写入操作。它适合在多读少写的场景中使用,能有效提高读取操作的并发性能。

示例代码及注释
#include <QReadWriteLock>
#include <QThread>
#include <iostream>

// 读写锁,保护共享资源
QReadWriteLock lock;
int sharedResource = 0;  // 共享资源

class Reader : public QThread {
public:
    void run() override {
        lock.lockForRead();  // 获取读锁
        std::cout << "Reader thread " << QThread::currentThreadId() << " reading: " << sharedResource << std::endl;
        lock.unlock();  // 释放读锁
    }
};

class Writer : public QThread {
public:
    void run() override {
        lock.lockForWrite();  // 获取写锁
        sharedResource++;
        std::cout << "Writer thread " << QThread::currentThreadId() << " writing: " << sharedResource << std::endl;
        lock.unlock();  // 释放写锁
    }
};

int main() {
    Reader reader1, reader2;  // 创建两个读线程
    Writer writer1;  // 创建一个写线程
    reader1.start();
    reader2.start();
    writer1.start();  // 写线程将阻塞后续的读操作
    reader1.wait();
    reader2.wait();
    writer1.wait();
    return 0;
}
解释:
  1. 多读单写lock.lockForRead() 允许多个线程同时读取,而 lock.lockForWrite() 则保证写操作期间的独占访问。
  2. 适用场景:在多读少写的场景下,读写锁可以提高效率。

4. 原子操作(QAtomicInt / std::atomic

原子操作是一种无需锁定的同步方式,适合处理简单的共享数据操作,如计数器的增减。它通过硬件保证操作的原子性,从而避免了线程间的数据竞争。

示例代码及注释
#include <QAtomicInt>
#include <QThread>
#include <iostream>

// 原子整型,用于原子递增
QAtomicInt atomicCounter = 0;

class Worker : public QThread {
public:
    void run() override {
        for (int i = 0; i < 1000; ++i) {
            atomicCounter.ref();  // 原子递增操作
        }
    }
};

int main() {
    Worker worker1, worker2;  // 创建两个线程
    worker1.start();
    worker2.start();
    worker1.wait();
    worker2.wait();
    std::cout << "Final counter: " << atomicCounter.load() << std::endl;  // 输出最终的计数值
    return 0;
}
解释:
  1. 轻量同步atomicCounter.ref() 是一个原子操作,不需要使用互斥锁,适合简单的递增或递减操作。
  2. 性能提升:相比于使用锁,原子操作的性能开销更小,非常适合对共享资源进行计数的场景。

5. 条件变量(QWaitCondition / std::condition_variable

条件变量用于线程间的同步,它允许线程等待某个条件被满足,通常与互斥锁一起使用。在条件满足时,可以唤醒一个或多个等待的线程。

示例代码及注释
#include <QMutex>
#include <QWaitCondition>
#include <QThread>
#include <iostream>

// 声明互斥锁和条件变量
QMutex mutex;
QWaitCondition condition;
bool ready = false;  // 条件标志

class Worker : public QThread {
public:
    void run() override {
        mutex.lock();  // 获取互斥锁
        while (!ready) {
            condition.wait(&mutex);  // 等待条件满足,期间释放互斥锁
        }
        std::cout << "Thread " << QThread::currentThreadId() << " is processing." << std::endl;
        mutex.unlock();  // 释放互斥锁
    }
};

int main() {
    Worker worker1, worker2;  // 创建两个线程
    worker1.start();
    worker2.start();
    
    QThread::sleep(1);  // 模拟主线程准备时间
    mutex.lock();
    ready = true;
    condition.wakeAll();  // 唤醒所有等待的线程
    mutex.unlock();

    worker1.wait();
    worker2.wait();
    return 0;
}
解释:
  1. 条件等待:线程使用 condition.wait() 进入等待状态,直到条件满足时被唤醒。
  2. 唤醒机制condition.wakeAll() 唤醒所有正在等待条件的线程,使它们重新进入竞争状态。

在多线程编程中,选择合适的同步机制是确保数据安全和程序高效运行的关键。根据不同的应用场景,互斥锁、信号量、读写锁、原子操作和条件变量各有优缺点:

  • 互斥锁 适用于保护临界区,确保共享资源的独占访问;
  • 信号量 控制并发资源的访问数量;
  • 读写锁 适合多读少写的场景,提升读取性能;
  • 原子操作 在简单的共享数据操作中高效且轻量;
  • 条件变量 允许线程等待某个条件的满足,以实现灵活的线程同步。

合理使用这些工具能够有效避免多线程编程中的竞争条件,确保程序的安全和性能。

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

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

相关文章

数据结构--单链表创建、增删改查功能以及与结构体合用

一、作业要求 单链表操作&#xff0c;要求节点是结构体类型&#xff0c;实现以下功能&#xff1a; 1.尾插学生 2.任意位置插入学生 3.任意位置删除学生 4.逆置单链表 5.学生按学号排序 6.销毁单链表 二、实现过程 1.代码如下&#xff1a; &#xff08;1&#xff09;头…

scanning folder for git repositories 当前没有源代码管理提供程序进行注册

这个问题困扰了我好几天。尝试了各种方法&#xff0c;虽然有了解决方法 。但是感觉根本原因还是没找到。解决方案是更改git的 openRepositoryInParentFolders 为always 。我之所以觉着没找到根本原因是因为 我远程另一个主机仍然使用prompt 确是正常的。 解决方案原文 https://…

数据防泄密系统有哪些|盘点2024年8款好用的防泄密系统!

“安全重于泰山&#xff0c;数据泄露如蚁穴溃堤。 ”在信息化高速发展的今天&#xff0c;数据已成为企业的核心资产&#xff0c;其安全性直接关系到企业的生存与发展。 因此&#xff0c;构建一套高效、全面的数据防泄密系统显得尤为重要。 本文将为您盘点2024年几款好用的数据…

FastDFS的docker部署及实现头像上传

FastDFS的使用并实现头像上传 一、FastDFS概述二、安装FastDFS1. 拉取镜像2. 安装tracker3. 安装storage4. NGINX服务启动三、配置和依赖配置依赖四、头像上传一、FastDFS概述 概述 上传交互过程 两个服务:Tracker Server 和Storage Server Tracker Server 跟踪服务,负责调…

②大缓存ModbusRTU485数据集中采集器寄存器线圈重映射从站并发采集Modbus 串口RS485 转 RS485

大缓存ModbusRTU485数据集中采集器寄存器线圈重映射从站并发采集https://item.taobao.com/item.htm?ftt&id811821574300 关于产品的布线图和配置说明 以一分4路&#xff08;MS-A1-C041&#xff09;为例 布线图 RS485 在点到多点通信时&#xff0c;为了防止信号的反射和干…

《线性代数》学渣笔记

文章目录 1 行列式1.1 克拉默法则1.2 基本性质1.3 余子式 M i j M_{ij} Mij​1.4 代数余子式 A i j ( − 1 ) i j ⋅ M i j A_{ij} (-1)^{ij} \cdot M_{ij} Aij​(−1)ij⋅Mij​1.5 具体型行列式计算&#xff08;化为基本型&#xff09;1.5.1 主对角线行列式&#xff1a;主…

MMD模型及动作一键完美导入UE5-IVP5U插件方案(二)

1、下载并启用IVP5U插件 1、下载IVP5U插件, IVP5U,点击Latest下载对应引擎版本,将插件放到Plugins目录,同时将.uplugin文件的EnableByDefault改为false 2、然后通过Edit->Plugins启用插件 2、导入pmx模型 1、直接在Content的某个目录拖入pmx模型,选择默认参数 2、…

13年408计算机考研-计算机网络

第一题&#xff1a; 解析&#xff1a;OSI体系结构 OSI参考模型&#xff0c;由下至上依次是&#xff1a;物理层-数据链路层-网络层-运输层-会话层-表示层-应用层。 A.对话管理显然属于会话层&#xff0c; B.数据格式转换&#xff0c;是表示层要解决的问题&#xff0c;很显然答案…

使用Python和OpenCV生成灰阶图像

代码如下&#xff1a; import cv2 import numpy as npimg np.zeros((256, 256), np.uint8)for i in range(0,16):for j in range(0,16):img[i*16:(i1)*16][j*16:(j1)*16]i*16jcv2.imwrite(result.jpg, img) 效果如下&#xff1a;

新能源汽车充电桩怎么选?

新能源汽车是我国七大战略性新兴产业之一&#xff0c;已成为汽车产业转型升级的重要推动力。毫无疑问。充电桩作为我国新能源汽车产业链下游的重要环节&#xff0c;在国家政策的大力支持和市场需求的带动下&#xff0c;有着非常广阔的前景。安科瑞叶西平187-06160015 新能源汽…

科研服务新高度:表观组学的一站式实验服务

生物信息实验室致力于分子育种技术的研发和在生物医学研究领域的应用&#xff0c;实验室以分子遗传学实验技术和高通量生物信息分析技术为核心&#xff0c;建立了基因组、表观组、互作组的全面科研服务体系。50余位教授、研究员智库专家&#xff0c;您身边的分子实验专家!

哈里斯表态:承诺支持加密货币投资!

KlipC报道&#xff1a;近日&#xff0c;在曼哈顿举办的一次筹款活动中&#xff0c;美国副总统哈里斯首次公开表态&#xff0c;如果当选&#xff0c;她将支持增加对人工智能和加密货币行业的投资。 哈里斯表示&#xff0c;“我将把劳工、小企业创始人、创新者和大公司团结在一起…

【网络安全】公钥密码体制

1. 公钥密码体制概述 1.1 基本概念 公钥密码体制&#xff0c;又称为非对称密码体制&#xff0c;是一种基于数学函数的加密方式&#xff0c;它使用一对公钥和私钥来进行加密和解密。公钥用于加密&#xff0c;私钥用于解密。这种体制提供了一种安全的通信方式&#xff0c;因此在…

安装程序不用鼠标,Windows也玩程序包管理存储库

网管小贾 / sysadm.cc “嘿&#xff0c;嘿&#xff0c;看见没&#xff0c;今年某某著名大学建筑专业才招了4名新生&#xff01;” 大刘用手点指手机&#xff0c;带着一脸的吃惊相。 我冲他笑了笑&#xff0c;说道&#xff1a;“那是他们的教学水平不行。” “要是换了我&…

【JS】正则表达

正则表达式 reg /匹配规则/ reg.test(str) 1.边界符&#xff1a;^ 以...开头&#xff0c;$ 以...结尾 2.量词&#xff1a;* 出现0次或多次&#xff0c; 出现1次或多次, ? 出现0次或1次,{n}出现n次&#xff0c;{n,m}出现n到m次 3.字符类&#xff1a;[]中的字符任一出现&…

无人机的避障的航迹规划详解!!!

一、无人机避障技术 视觉避障系统&#xff1a;通过安装在无人机上的摄像头捕捉周围环境的图像&#xff0c;利用计算机视觉技术对图像进行处理和分析&#xff0c;提取出障碍物的信息。这种方法直观、信息丰富&#xff0c;但在光线不足或变化多的情况下可能影响识别效果&#xf…

生成测试图片的步骤

生成测试图片的步骤&#xff1a; 1、通义万象画图&#xff1a;https://tongyi.aliyun.com/wanxiang/creation 2、改图宝修改尺寸&#xff1a;https://www.gaitubao.com/

set的使用

序列式容器和关联式容器 序列式容器&#xff1a; 前⾯我们已经接触过STL中的部分容器如&#xff1a;string、vector、list、deque、array、forward_list等&#xff0c;这些容器统称为序列式容器&#xff0c;因为逻辑结构为线性序列的数据结构&#xff0c;两个位置存储的值之间…

【Python报错已解决】AttributeError: ‘Tensor‘ object has no attribute ‘kernel_size‘

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 专栏介绍 在软件开发和日常使用中&#xff0c;BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…

使用compile_commands激活vscode索引-跳转-代码提示功能

最近发现&#xff0c;使用vscode打开一个大的c工程很容易无法正常调转和代码提示。所以经常会手动修改.vscode/c_cpp_properties.json文件的"includePath"属性。然而&#xff0c;当pkg越来越多 工程体量越来越大之后&#xff0c;我不得不探索如何自动的完成这一过程&…