深入理解C++中的锁
目录
1.基本互斥锁(std::mutex)
2.递归互斥锁(std::recursive_mutex)
3.带超时机制的互斥锁(std::timed_mutex)
4.带超时机制的递归互斥锁(std::recursive_timed_mutex)
7.自旋锁
8.总结
1.基本互斥锁(std::mutex)
含义: std::mutex
是最基本的互斥锁,主要用于保护临界区,确保同一时间只有一个线程可以访问共享资源。
使用场景: 当需要保护共享资源不被多个线程同时修改时使用。
特点:简单易用,适用于大多数场景;不能递归锁定,同一线程多次尝试锁定会导致死锁。
以下是一个简单的示例,展示了如何使用 std::mutex
来保护共享数据:
#include <iostream>
#include <thread>
#include <mutex> std::mutex mtx; //全局互斥锁
int shared_data = 0; //共享数据void increment_shared_data(int n) { for (int i = 0; i < n; ++i) { std::lock_guard<std::mutex> lock(mtx); ++shared_data; }
} int main() { std::thread t1(increment_shared_data, 1000); std::thread t2(increment_shared_data, 1000); t1.join(); t2.join(); std::cout << "Shared data: " << shared_data << std::endl; return 0;
}
这个程序创建了2个线程,每个线程尝试对counter
增加10000次。通过使用std::mutex
, 我们确保每次只有一个线程可以增加计数器,避免了数据竞争。
2.递归互斥锁(std::recursive_mutex)
含义:std::recursive_mutex
允许同一线程多次获取锁而不会发生死锁,这对于递归函数或需要多次锁定的场景非常有用。
使用场景: 在递归函数中需要多次获取同一个锁的情况。
特点:适用于递归调用和需要多次锁定的场景;需要注意避免滥用,因为递归锁的使用会增加锁定次数的复杂性。
示例如下:
#include <iostream>
#include <thread>
#include <mutex>std::recursive_mutex rmtx;void recursive_function(int depth) {rmtx.lock();std::cout << "Depth: " << depth << std::endl;if (depth > 0) {recursive_function(depth - 1);}rmtx.unlock();
}int main() {std::thread t(recursive_function, 5);t.join();return 0;
}
这段代码在递归函数recursive_function
中使用std::recursive_mutex
。每次调用都会尝试加锁,由于使用的是递归互斥锁,同一线程可以多次成功获取锁。
3.带超时机制的互斥锁(std::timed_mutex)
含义:std::timed_mutex
在std::mutex
的基础上增加了超时功能,允许线程在指定时间内尝试获取锁,如果在超时时间内未成功获取锁,则返回失败。
使用场景: 当你不希望线程因等待锁而无限期阻塞时使用。
特点:适用于需要设置锁获取超时时间的场景;提供try_lock_for
和try_lock_until
两种超时尝试获取锁的方法。
示例如下:
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono> std::timed_mutex mtx; void try_lock_function() { if (mtx.try_lock_for(std::chrono::seconds(1))) { std::cout << "Lock acquired!\n"; // 执行受保护的操作 std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作 mtx.unlock(); // 显式解锁 } else { std::cout << "Failed to acquire lock within timeout.\n"; }
} int main() { std::thread t1(try_lock_function); std::thread t2(try_lock_function); t1.join(); t2.join(); return 0;
}
在这个例子中,两个线程都尝试在 1 秒内获取锁。由于互斥锁在同一时刻只能被一个线程持有,因此至少有一个线程将无法在超时时间内获取锁,并输出相应的消息。
4.带超时机制的递归互斥锁(std::recursive_timed_mutex)
含义:std::recursive_timed_mutex
结合了std::recursive_mutex
和std::timed_mutex
的特性,支持递归锁定和超时机制。
使用场景: 适用于需要递归锁定资源,并且希望能够设置尝试获取锁的超时时间的场景。这在需要防止线程在等待锁时无限阻塞的复杂递归调用中特别有用。
特点:适用于递归调用和需要超时机制的场景;提供超时尝试获取递归锁的方法。
示例如下:
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono> std::recursive_timed_mutex mtx; void recursive_lock_function() { if (mtx.try_lock_for(std::chrono::seconds(1))) { std::cout << "Lock acquired!\n"; // 递归锁定 if (mtx.try_lock_for(std::chrono::seconds(1))) { std::cout << "Recursive lock acquired!\n"; mtx.unlock(); // 释放递归锁 } else { std::cout << "Failed to acquire recursive lock within timeout.\n"; } // ... 执行受保护的操作 mtx.unlock(); // 释放原始锁 } else { std::cout << "Failed to acquire lock within timeout.\n"; }
} int main() { std::thread t1(recursive_lock_function); std::thread t2(recursive_lock_function); t1.join(); t2.join(); return 0;
}
请注意,由于 std::recursive_timed_mutex
允许递归锁定,上面的示例中展示了如何在已经持有锁的情况下再次尝试获取锁(尽管在这个特定示例中,第二次尝试获取锁是多余的,因为我们已经持有锁了)。然而,在实际情况中,递归锁定可能用于更复杂的场景,其中函数可能会递归调用自己,并且每个递归调用都需要访问受保护的数据。
5.共享互斥锁也叫读写锁(std::shared_mutex)
含义:std::shared_mutex
允许多个线程同时读取,但只有一个线程可以写入。这在读多写少的场景下非常有用。
使用场景: 适用于读操作远多于写操作的情况。
特点:适用于读多写少的场景;读操作和写操作使用不同的锁定机制。
示例如下:
#include <iostream>
#include <thread>
#include <shared_mutex>std::shared_mutex shmtx;void read_shared(int id) {std::shared_lock<std::shared_mutex> lock(shmtx); // 共享锁std::cout << "Thread " << id << " is reading" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(100));
}void write_shared(int id) {std::unique_lock<std::shared_mutex> lock(shmtx); // 独占锁std::cout << "Thread " << id << " is writing" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(100));
}int main() {std::thread readers[5], writer(write_shared, 1);for (int i = 0; i < 5; ++i) {readers[i] = std::thread(read_shared, i + 2);}writer.join();for (auto& reader : readers) {reader.join();}return 0;
}
输出结果可能会有所不同,因为读写顺序由操作系统的线程调度决定。本例中,一个写线程在修改数据,多个读线程在同时读数据。通过std::shared_mutex
,我们允许多个读操作同时进行,但写操作是独占的。
6.带超时机制的共享互斥锁(std::shared_timed_mutex)
含义:std::shared_timed_mutex
是 C++ 标准库中的一个同步原语,它结合了 std::shared_mutex
(共享互斥锁)和超时机制的特性。std::shared_mutex
允许多个线程同时以共享模式持有锁(即读取操作可以并发执行),但每次只有一个线程能以独占模式持有锁(即写入操作是互斥的)。通过添加超时机制,std::shared_timed_mutex
允许线程尝试以共享模式或独占模式获取锁,并设置一个超时时间,如果在这段时间内未能成功获取锁,则可以放弃并继续执行其他操作。
使用场景:当你不希望线程因等待锁而无限期阻塞时使用。
特点:适用于读多写少且需要超时机制的场景;提供超时尝试获取共享锁的方法。
示例如下:
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <chrono>std::shared_timed_mutex shtmmtx;void try_read_shared(int id) {if (shtmmtx.try_lock_shared_for(std::chrono::milliseconds(100))) {std::cout << "Thread " << id << " is reading" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(50));shtmmtx.unlock_shared();} else {std::cout << "Thread " << id << " could not read" << std::endl;}
}void try_write_shared(int id) {if (shtmmtx.try_lock_for(std::chrono::milliseconds(100))) {std::cout << "Thread " << id << " is writing" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(50));shtmmtx.unlock();} else {std::cout << "Thread " << id << " could not write" << std::endl;}
}int main() {std::thread readers[5], writer(try_write_shared, 1);for (int i = 0; i < 5; ++i) {readers[i] = std::thread(try_read_shared, i + 2);}writer.join();for (auto& reader : readers) {reader.join();}return 0;
}
7.自旋锁
含义:在C++中,自旋锁(spinlock)是一种低级的同步机制,用于保护共享资源,防止多个线程同时访问。与互斥锁(mutex)不同,当自旋锁被锁定时,尝试获取锁的线程会不断循环检查锁是否可用,而不是进入睡眠状态等待锁被释放。这意味着,自旋锁在等待时间很短的情况下是非常有效的,但如果等待时间过长,会导致CPU资源的浪费。
C++标准库本身并不直接提供自旋锁的实现,但你可以使用<atomic>
库中的原子操作来手动实现一个自旋锁,或者使用特定平台提供的API(如Windows的SRWLOCK
或POSIX的pthread_spinlock_t
)。
使用场景:自旋锁适用于锁持有时间非常短且线程不希望在操作系统调度中频繁上下文切换的场景。这通常用在低延迟系统中,或者当线程数量不多于CPU核心数量时,确保CPU不会在等待锁时空闲。
示例如下:
#include <atomic>
#include <iostream>
#include <thread>
#include <chrono> class Spinlock {
private: std::atomic_flag lock_ = ATOMIC_FLAG_INIT; public: void lock() { while (lock_.test_and_set(std::memory_order_acquire)) { // 循环直到锁被释放 } } void unlock() { lock_.clear(std::memory_order_release); } bool try_lock() { return !lock_.test_and_set(std::memory_order_acquire); }
}; void threadFunction(Spinlock& lock, int id) { lock.lock(); std::cout << "Thread " << id << " entered critical section\n"; std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟耗时操作 std::cout << "Thread " << id << " leaving critical section\n"; lock.unlock();
} int main() { Spinlock lock; std::thread t1(threadFunction, std::ref(lock), 1); std::thread t2(threadFunction, std::ref(lock), 2); t1.join(); t2.join(); return 0;
}
在这个例子中,Spinlock
类使用了一个 std::atomic_flag
类型的成员变量 lock_
来实现锁的功能。lock_
的 test_and_set
方法会尝试将标志设置为 true
并返回之前的值。如果返回 false
,表示锁之前未被锁定,当前线程成功获取锁;如果返回 true
,表示锁已被其他线程持有,当前线程需要继续循环等待。
请注意,自旋锁在多核处理器上且等待时间较短时通常表现良好,但在等待时间较长或锁竞争激烈时可能会导致性能问题。因此,在选择使用自旋锁时,需要根据具体的应用场景和性能要求做出合理的选择。
8.总结
C++标准库提供了多种类型的互斥锁,每种锁都有其特定的用途和特点。选择合适的互斥锁类型可以有效提高程序的并发性能和安全性。
C++惯用法之RAII思想: 资源管理_raii 思想-CSDN博客
相关文章:

深入理解C++中的锁
目录 1.基本互斥锁(std::mutex) 2.递归互斥锁(std::recursive_mutex) 3.带超时机制的互斥锁(std::timed_mutex) 4.带超时机制的递归互斥锁(std::recursive_timed_mutex) 5.共享…...

压缩pdf文件大小,压缩pdf文件大小软件哪个好
在数字化时代,PDF文件因其卓越的跨平台兼容性和稳定性而成为工作与学习的好帮手。然而,当PDF文件体积过大时,传输和存储便成了一项挑战。别担心,本文将为你揭秘如何快速压缩PDF文件,让你的文档轻装上路! 压…...

难道 Java 已经过时了?
当一门技术已经存在许多年了,它可能会失去竞争力,而后黯然退场,默默地离开,这对大部分的人来说就已经算是过时了。 Java 于 1995 年正式上线,至今已经走过了 27 个年头,在众多编程技术里算是年龄比较大的语…...

华为OD机考题(HJ32 密码截取)
前言 经过前期的数据结构和算法学习,开始以OD机考题作为练习题,继续加强下熟练程度。有需要的可以同步练习下。 描述 Catcher是MCA国的情报员,他工作时发现敌国会用一些对称的密码进行通信,比如像这些ABBA,ABA&…...

【高考志愿】测绘科学与技术
目录 一、专业介绍 1.1 专业概述 1.2 专业方向 1.3 课程内容 二、就业前景 三、报考注意事项 四、测绘科学与技术专业排名 五、职业规划与未来发展 高考志愿选择测绘科学与技术专业,对于许多有志于空间信息技术领域发展的学生来说,无疑是一个极具…...

SpringBoot异步接口实现 提升吞吐量
前言 Servlet 3.0之前:HTTP请求由单一线程处理。Servlet 3.0之后:支持异步处理,提高系统吞吐量。 SpringBoot 异步接口实现方式 AsyncContext:Servlet层级,不常用。Callable:使用java.util.concurrent.C…...

C语言快速学习笔记
学习网站:C 语言教程 | 菜鸟教程 (runoob.com)C 语言教程 | 菜鸟教程 (runoob.com)C 语言教程 | 菜鸟教程 (runoob.com) 这个网站知识完整,讲解清晰。 在线C语言编程工具:菜鸟教程在线编辑器 (runoob.com) 国外学习网站:C语言介…...

如何选择易用性高的项目管理软件?
随着项目管理在各行各业的广泛应用,选择一款易用性高的项目管理软件变得越来越重要。易用性高的软件可以帮助企业提高工作效率,降低管理成本,同时还能提升团队之间的协作能力。那么,如何选择一款易用性高的项目管理软件呢…...

vue3基于uni-app 封装小程序request请求
const BASE_URL https://47.122.26.142; // 替换为你的 API 基础 URL const token uni.getStorageSync(token);const request (url: string, method: any, data {}, headers {}) > {return new Promise((resolve, reject) > {uni.request({url: ${BASE_URL}${url},m…...

YOLO在目标检测与视频轨迹追踪中的应用
YOLO在目标检测与视频轨迹追踪中的应用 引言 在计算机视觉领域,目标检测与视频轨迹追踪是两个至关重要的研究方向。随着深度学习技术的飞速发展,尤其是卷积神经网络(CNN)的广泛应用,目标检测与视频轨迹追踪的性能得到…...

版本控制系统:Git 纯应用(持续更新)
基本操作 ctrl上行键:上次代码 本地仓库:Git init 新建文件:touch xxxx.xxx 查看状态:Git status 文件从工作区——暂存区:Git add ./文件名(.是通配符代表所有) 暂存区——仓库:Git commit -m &…...

从0开始搭建vue项目
#先查下电脑有没有安装过node和npm node -v npm -v #安装vue npm install -g vue #安装webpack npm install webpack -g 都安装好后,进入你想创建的文件夹内 创建名字:vue init webpack <project_name> 就默认回车 然后根据项目需求Y/n 比如…...

Java框架常见面试题
在Java框架面试中,面试官通常会考察候选人对常见Java框架的理解、使用经验以及解决问题的能力。以下是一些常见的Java框架面试题及其详细回答: 1. Spring框架相关问题 问题:Spring框架的核心组件有哪些?它们各自的作用是什么&am…...

linux c 应用编程定时器函数
在 Linux C 应用编程中,对于多线程编程中的定时器函数使用,通常可以借助 pthread 库和系统提供的定时器相关的函数来实现。 首先,常见的定时器函数有 setitimer() 和 alarm() 。setitimer() 函数可以更精确地设置定时器,它可以设…...

设备调试上位机GUI
C Fast Qt C 前端 原来真的不需要在 design 上画来画去,有chat-gpt 那里不知道问哪里 全是组件拼起来的,不需要画,最后发现其实也是定式模式,跟着AI 学套路 最终前端界面 鼠标邮件绑定几个功能 太nice 了 在再加一个全局的日志模块 yyds MVC 的架构, 视图…...

项目管理系统厂商:奥博思发布《项目管理系统助力 IPD 高效落地》演讲
一场题为:“标准为基,项目之上 ,持续提升 PMO 卓越中心”的全国 PMO 专业人士年度盛会在京召开。会议围绕 PMO 卓越中心能力提升、项目管理标准化、项目管理体系建设等核心话题力邀业界专家、卓有建树的 PMO 实践精英来演讲、交流、分享。 奥…...

Java项目总结1
1.什么是面向对象(此对象非彼对象) “面向对象的方法主要是把事物给对象化,包括其属性和行为。面向对象编程更贴近实际生活的思想。总体来说面向对象的底层还是面向过程,面向过程抽象成类,然后封装,方便使用…...

Java中的类加载机制详解
Java中的类加载机制详解 大家好,我是微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿! 类加载机制概述 在Java中,类加载机制是Java虚拟机(JVM)将.class文件加载到内存中并转换…...

SwiftUI 中 Grid 内多个 NavigationLink 同时发生导航之诡异问题的解决
问题现象 不知小伙伴们发现了没有?在 SwiftUI 中如果有多个 NavigationLink 视图嵌入在 Grid(包括 LazyVGrid 和 LazyHGrid)容器中,点击其中任意一个 NavigationLink 都会导致所有导航一起发生。 如上图所示,点击 Grid 中任何一个 NavigationLink,所有 NavigationLink 都…...

51单片机第21步_将TIM0用作两个8位定时器同时将TIM1用作波特率发生器
本章重点讲解将TIM0用作两个8位定时器,同时将TIM1用作波特率发生器。 当定时器T0在方式3时,T1不能产生中断,但可以正常工作在方式0、1、2下,大多数情况下,T1将用作串口的波特率发生器。 1、定时器0工作在模式3框图&a…...

API-元素尺寸与位置
学习目标: 掌握元素尺寸与位置 学习内容: 元素尺寸与位置仿京东固定导航栏案例实现bilibili点击小滑块移动效果 元素尺寸与位置: 使用场景: 前面案例滚动多少距离,都是我们自己算的,最好是页面滚动到某个…...

C语言中的基础指针操作
在C语言中,指针是一个非常重要的概念,它提供了直接访问内存地址的能力。指针变量用于存储内存地址,而不是数据值,在某种意义上和门牌号具有相似含义:指针是一个变量,其存储的是另一个变量的内存地址&#x…...

LabVIEW环境下OCR文字识别的实现策略与挑战解析
引言 在自动化测试领域,OCR(Optical Character Recognition,光学字符识别)技术扮演着重要角色,它能够将图像中的文字转换成机器可编辑的格式。对于使用LabVIEW约5个月,主要进行仪器控制与数据采集的你而言…...

中英双语介绍美国的州:堪萨斯州(Kansas)
中文版 堪萨斯州简介 堪萨斯州(Kansas)位于美国中部,以其广阔的平原、丰富的农业资源和多样的文化遗产而著称。以下是对堪萨斯州的详细介绍,包括其地理位置、人口、经济、教育、文化和主要城市。 地理位置 堪萨斯州位于美国中…...

信息收集---端口服务信息收集
1. 什么是端口 是设备与外界通讯交流的出口。端口可分为虚拟端口和物理端口,其中虚拟端口指计算机内部或交换机路由器内的端口,不可见。例如计算机中的80端口、21端口、23端口等。物理端口又称为接口,是可见端口,计算机背板的RJ45…...

Python知识点背诵手册,超详细知识梳理
一、手册介绍 《Python知识点背诵手册》是一份详尽的Python学习资料,旨在帮助学习者系统地掌握Python语言的基础知识和进阶技能。该手册将Python的所有关键语法和概念进行了精炼的总结,并以易于理解和记忆的方式呈现。以下是手册的主要特点和内容概述&a…...

【Pytorch实用教程】如何在多个GPU上使用分布式数据并行进行训练模型
文章目录 1. 代码(可直接运行,含随机生成的训练数据)2. 代码的详细解释2.1. 导入必要的库和模块2.2. 设置每个进程的初始设置2.3. 随机生成数据集类 `RandomDataset`2.4. 训练函数 `train`1. 代码(可直接运行,含随机生成的训练数据) 以下是一个基于PyTorch的多GPU分布式…...

PIL,OpenCV,Pytorch处理图像时的通道顺序(颜色,长宽深)
项目颜色通道顺序长宽通道顺序数据类型取值范围PILRGBHWCndarray0-255 (byte)OpenCVBGRHWCndarray0-255 (byte)PyTorchRGB/BGR (取决于如何读取)(N)CHWtensor0-1 (float, 标准化后); 0-255 (int, 未标准化) 注意以下几点: 颜色通道顺序:PIL默认使用RGB顺…...

经纬恒润亮相2024世界智能产业博览会
近日,以“智行天下 能动未来”为主题的2024世界智能产业博览会(以下简称“智博会”)在国家会展中心(天津)成功举办。本次智博会上,经纬恒润自主研发的汽车电子产品联合天津(西青)国家…...

Python序列化和反序列化
一.序列化和反序列化 在Python中,序列化(Serialization)和反序列化(Deserialization)是处理对象数据的过程,主要用于对象的存储或网络传输。 序列化(Serialization) 序列化是将Pyth…...