RAII 与 std::lock_guard 在 C++ 中的应用:自动化互斥锁管理与线程安全
目录
1. RAII(资源获取即初始化)概述
RAII 的优点
2. std::lock_guard 的工作原理
2.1 构造函数
2.2 析构函数
2.3 关键特性
3. 为什么 std::lock_guard 能自动管理锁的生命周期
3.1 RAII 原则的应用
3.2 异常安全
3.3 简化代码和减少错误
4. 代码示例分析
示例代码
执行流程
输出示例
5. std::lock_guard 的替代方案
5.1 std::unique_lock
5.2 std::shared_lock
6. 总结
std::lock_guard<std::mutex>
能够自动管理锁的生命周期,主要得益于 RAII(资源获取即初始化) 这一编程范式。
1. RAII(资源获取即初始化)概述
RAII 是一种常用的编程技术,尤其在 C++ 中广泛应用。其核心思想是将资源的获取和释放绑定到对象的生命周期上,即:
- 资源获取:在对象的构造函数中获取资源。
- 资源释放:在对象的析构函数中释放资源。
通过这种方式,可以确保资源在对象生命周期内被正确管理,避免资源泄漏和其他管理错误。
RAII 的优点
- 自动管理资源:无需手动调用释放资源的函数,减少人为错误。
- 异常安全:即使在异常发生时,析构函数也会被调用,确保资源被释放。
- 简化代码:减少显式的资源管理代码,使代码更加简洁和易读。
2. std::lock_guard
的工作原理
std::lock_guard<std::mutex>
是一个遵循 RAII 原则的类模板,用于管理互斥锁(std::mutex
)的生命周期。它的设计确保了锁在对象的整个生命周期内被持有,并在对象销毁时自动释放锁。
2.1 构造函数
当创建一个 std::lock_guard<std::mutex>
对象时,其构造函数会自动调用互斥锁的 lock()
方法,获取锁。
std::mutex mtx;void example() {std::lock_guard<std::mutex> lock(mtx); // 构造时自动调用 mtx.lock()// 临界区代码
} // lock 对象析构时自动调用 mtx.unlock()
2.2 析构函数
当 std::lock_guard<std::mutex>
对象超出其作用域时,其析构函数会自动调用互斥锁的 unlock()
方法,释放锁。
{std::lock_guard<std::mutex> lock(mtx); // 获取锁// 临界区代码
} // lock 对象析构,自动释放锁
2.3 关键特性
-
不可复制和不可移动:
std::lock_guard
禁止复制和移动,以确保锁的唯一所有权,防止多个lock_guard
对象管理同一把锁。std::lock_guard<std::mutex> lock1(mtx); // std::lock_guard<std::mutex> lock2 = lock1; // 编译错误
-
轻量级:
std::lock_guard
本身不占用过多资源,主要负责锁的获取和释放。
3. 为什么 std::lock_guard
能自动管理锁的生命周期
3.1 RAII 原则的应用
std::lock_guard
遵循 RAII 原则,通过其构造函数和析构函数自动管理锁的获取和释放:
- 构造阶段:当
lock_guard
对象被创建时,自动获取锁。 - 析构阶段:当
lock_guard
对象被销毁时,自动释放锁。
这种设计使得锁的管理与对象的生命周期紧密绑定,无需手动干预。
3.2 异常安全
在临界区代码中,如果发生异常,lock_guard
的析构函数仍会被调用,确保锁被正确释放,防止死锁。
void example() {std::lock_guard<std::mutex> lock(mtx); // 获取锁// 临界区代码throw std::runtime_error("Error occurred"); // 异常发生
} // lock 对象析构,自动释放锁
上述代码中,即使在临界区抛出异常,lock_guard
仍会在栈展开过程中被销毁,自动调用 mtx.unlock()
,释放锁。
3.3 简化代码和减少错误
使用 std::lock_guard
可以显著简化代码,避免手动调用 lock()
和 unlock()
可能导致的错误,如忘记释放锁、异常情况下未释放锁等。
手动管理锁的风险:
void risky_example() {mtx.lock();// 临界区代码if (some_condition) {mtx.unlock(); // 可能被遗漏return;}// 更多代码mtx.unlock(); // 可能未被执行
}
使用 std::lock_guard
的安全性:
void safe_example() {std::lock_guard<std::mutex> lock(mtx); // 自动获取锁// 临界区代码if (some_condition) {return; // 自动释放锁}// 更多代码
} // 自动释放锁
4. 代码示例分析
让我们通过一个具体的代码示例,进一步理解 std::lock_guard
如何自动管理锁的生命周期。
示例代码
#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx;void print_thread_id(int id) {std::lock_guard<std::mutex> lock(mtx); // 构造时获取锁std::cout << "Thread " << id << " is running.\n";// 析构时自动释放锁
}int main() {std::thread t1(print_thread_id, 1);std::thread t2(print_thread_id, 2);t1.join();t2.join();return 0;
}
执行流程
-
创建
lock_guard
对象:- 当
lock
对象被创建时,调用mtx.lock()
,获取锁。
- 当
-
执行临界区代码:
- 输出线程 ID,确保输出操作是线程安全的,不会被其他线程打断。
-
析构
lock_guard
对象:- 当
lock
对象超出作用域(函数返回或异常发生时),调用mtx.unlock()
,释放锁。
- 当
输出示例
Thread 1 is running.
Thread 2 is running.
确保每个线程在输出时都持有锁,避免输出内容交错。
5. std::lock_guard
的替代方案
虽然 std::lock_guard
是管理锁的简单而高效的方式,但在某些情况下,其他锁管理工具可能更适合:
5.1 std::unique_lock
- 更灵活:允许手动锁定和解锁,可以延长锁的持有时间。
- 支持延迟锁定:可以在构造时不立即获取锁。
- 适用于需要更复杂锁管理的场景。
示例:
std::mutex mtx;void example() {std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 不立即获取锁// 其他操作lock.lock(); // 手动获取锁// 临界区代码// lock 会在析构时自动释放锁
}
5.2 std::shared_lock
- 适用于共享锁:允许多个线程同时读取共享资源,但写操作需要独占锁。
- 与
std::shared_mutex
搭配使用。
示例:
#include <shared_mutex>std::shared_mutex shared_mtx;void read_operation() {std::shared_lock<std::shared_mutex> lock(shared_mtx); // 共享锁// 读取共享资源
}void write_operation() {std::unique_lock<std::shared_mutex> lock(shared_mtx); // 独占锁// 写入共享资源
}
6. 总结
std::lock_guard<std::mutex>
通过 RAII 原则,利用构造和析构函数自动管理锁的获取和释放,确保了线程安全性并简化了代码。其主要优势包括:
- 自动锁管理:减少手动锁定和释放的错误风险。
- 异常安全:即使在异常情况下,锁也能被正确释放。
- 代码简洁:使用
lock_guard
可以让代码更加清晰和易维护。
通过遵循 RAII 原则,std::lock_guard
为多线程编程提供了一种高效、安全且易于使用的锁管理方式,是现代 C++ 中推荐的锁管理工具。
相关文章:

RAII 与 std::lock_guard 在 C++ 中的应用:自动化互斥锁管理与线程安全
目录 1. RAII(资源获取即初始化)概述 RAII 的优点 2. std::lock_guard 的工作原理 2.1 构造函数 2.2 析构函数 2.3 关键特性 3. 为什么 std::lock_guard 能自动管理锁的生命周期 3.1 RAII 原则的应用 3.2 异常安全 3.3 简化代码和减少错误 4.…...

风格汇:奢华风格在UI设计中如何被定义的。
在UI设计中,奢华风格通常指的是一种高端、豪华、精致的设计风格,旨在营造出奢华、豪华的视觉效果,给用户带来高品质、高档次的感受。 奢华风格的UI设计通常会运用一些富丽堂皇的元素和效果,例如金色、银色、贵族紫、华丽的字体、华…...

Vue2 qrcode+html2canvas 实现二维码的生成和保存
1.安装 npm install qrcode npm install html2canvas 2.引用 import QRCode from qrcode import html2canvas from html2canvas 效果: 1. 二维码生成: 下载二维码图片: 二维码的内容: 实现代码: <template>…...

GEE 教程:利用Google Dynamic数据进行逐月指定区域的土地分类数据提取分析
目录 简介 数据 代码 结果 简介 利用Google Dynamic数据进行逐月指定区域的土地分类数据提取分析 数据 Google Dynamic数据是指由Google自动生成、自动更新的数据,它不需要人工干预,而是通过算法和机器学习技术从各种来源获取并解析数据。这些数据可以是来自互联网上的…...

Nginx 负载均衡:优化网站性能与可扩展性的利器
在当今高流量的互联网时代,网站的性能和可扩展性成为了衡量其成功与否的关键因素之一。随着用户量的不断增加,单一服务器往往难以承受巨大的访问压力,这时就需要引入负载均衡技术来分散请求,提高系统的整体性能和可靠性。Nginx&am…...

【Python基础】Python错误和异常处理(详细实例)
本文收录于 《Python编程入门》专栏,从零基础开始,分享一些Python编程基础知识,欢迎关注,谢谢! 文章目录 一、前言二、Python中的错误类型三、Python异常处理机制3.1 try-except语句3.2 try-except-else语句3.3 try-fi…...

如何查看串口被哪个程序占用?截止目前最方便的方法
痛点:串口因为某种原因被占用,如何找到罪魁祸首? 做开发的小伙伴们,经常会遇到这样的问题:串口因为某种原因被占用,导致无法通讯,但是又找不到被哪个程序占用。只有重启电脑,才能解…...

深入理解SpringBoot(一)----SpringBoot的启动流程分析
1、SpringApplication 对象实例化 SpringApplication 文件 public static ConfigurableApplicationContext run(Object[] sources, String[] args) {// 传递的source其实就是类Bootstrapreturn new SpringApplication(sources).run(args);// 实例化一个SpringApplication对象执…...

MySql基础-单表操作
1. MYSQL概述 1.1 数据模型 关系型数据库 关系型数据库(RDBMS):建立在关系模型基础上,由多张相互连接的二维表组成的数据库。 特点: 使用表存储数据,格式统一,便于维护 使用SQL语言操作,标准统一&…...

【STM32系统】基于STM32设计的SD卡数据读取与上位机显示系统(SDIO接口驱动、雷龙SD卡)——文末资料下载
基于STM32设计的SD卡数据读取与上位机显示系统 演示视频: 基于STM32设计的SD卡数据读取与上位机显示系统 简介:本研究的主要目的是基于STM32F103微控制器,设计一个能够读取SD卡数据并显示到上位机的系统。SD卡的数据扇区读取不仅是为了验证存…...

SpringBoot开发——整合Redis
文章目录 1、创建项目,添加Redis依赖2、创建实体类Student3、创建Controller4、配置application.yml5、整合完成 Redis ( Remote Dictionary Server )是一个开源的内存数据库,遵守 BSD 协议,它提供了一个高性能的键值(…...

OpenCV结构分析与形状描述符(17)判断轮廓是否为凸多边形的函数isContourConvex()的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 测试轮廓的凸性。 该函数测试输入的轮廓是否为凸的。轮廓必须是简单的,即没有自相交。否则,函数的输出是不确定的。 cv:…...
P5425 [USACO19OPEN] I Would Walk 500 Miles G
*原题链接* 很离谱的题。首先可以想到暴力连边,整个图为一个完全图,将所有的边选出来,然后从小到大一条条加入,当剩下集合数量 <K 的时候就结束。答案为加入的最后一条边的大小。如果用prim算法的话时间复杂度为。足以通过此题…...

Java高级Day41-反射入门
115.反射 反射机制 1.根据配置文件re.properties指定信息,创建Cat对象并调用hi方法 SuppressWarnings({"all"}) public class ReflectionQuestion {public static void main(String[] args) throws IOException {//根据配置文件 re.properties 指定信息…...

在Linux系统上使用Docker部署java项目
一.使用Docker部署的好处: 在Linux系统上使用Docker部署项目通常会大大简化部署流程,因为Docker可以将应用程序及其依赖打包到一个独立的容器中。 Docker打包应用程序时会将其与所有依赖项(操作系统、库等)一起打包。这样&#…...

【C++】标准库IO查漏补缺
【C】标准库 IO 查漏补缺 文章目录 系统I/O1. 概述2. cout 与 cerr3. cerr 和 clog4. 缓冲区5. 与 printf 的比较 系统I/O 1. 概述 标准库提供的 IO 接口,包含在 iostream 文件中 输入流: cin输出流:cout / cerr / clog。 输入流只有一个 cin&#x…...

python简单易懂的lxml读取HTML节点及常用操作方法
python简单易懂的lxml读取HTML节点及常用操作方法 1. 初始化和基本概念 lxml 是一个强大的pyth库,用于处理XML和HTML文档。它提供了类似BeautifulSoup的功能,但性能更高。在使用lxml时,通常会先解析HTML或XML文档,得到一个Eleme…...

Java | Leetcode Java题解之第406题根据身高重建队列
题目: 题解: class Solution {public int[][] reconstructQueue(int[][] people) {Arrays.sort(people, new Comparator<int[]>() {public int compare(int[] person1, int[] person2) {if (person1[0] ! person2[0]) {return person2[0] - perso…...

安卓获取apk的公钥,用于申请app备案等
要申请app的icp备案等场景,需要app的 证书MD5指纹和公钥,示例如下: 步骤1:使用keytool从APK中提取证书 1. 打开命令行,cd 到你的apk目录,如:app/release 2. 解压APK文件: unzip yo…...

【leetcode_python】杨辉三角
给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中,每个数是它左上方和右上方的数的和。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]示例 2: 输入: numRows 1 输出: [[1]] 方案&#…...

Parallels Desktop 20 for Mac中文版发布了?会哪些新功能
Parallels Desktop 20 for Mac 正式发布,完全支持 macOS Sequoia 和 Windows 11 24H2,并且在企业版中引入了全新的管理门户。 据介绍,新版本针对 Windows、macOS 和 Linux 虚拟机进行了大量更新,最大的亮点是全新推出的 Parallels…...

SpringBoot整合SSE-灵活管控连接
SpringBoot整合SSE(管控连接) 1、sse单向通信整成逻辑双向通信。 2、轻量级实现端对端信息互通。 3、避免繁琐配置学习。 核心点通过记录连接码和心跳检测实现伪双向通道,避免无效连接占用过多内存。 服务器推送(Server Push)技术允许网站和应用在有新内容可用时主动向用户…...

挖矿木马-Linux
目录 介绍步骤 介绍 1、挖矿木马靶机中切换至root用户执行/root目录下的start.sh和attack.sh 2、题目服务器中包含两个应用场景,redis服务和hpMyAdmin服务,黑客分别通过两场景进行入侵,入侵与后续利用线路路如下: redis服务&…...

【leetcode——415场周赛】——python前两题
3289. 数字小镇中的捣蛋鬼 数字小镇 Digitville 中,存在一个数字列表 nums,其中包含从 0 到 n - 1 的整数。每个数字本应 只出现一次,然而,有 两个 顽皮的数字额外多出现了一次,使得列表变得比正常情况下更长。 为了…...

【CSS in Depth 2 精译_029】5.2 Grid 网格布局中的网格结构剖析(上)
当前内容所在位置(可进入专栏查看其他译好的章节内容) 第一章 层叠、优先级与继承(已完结) 1.1 层叠1.2 继承1.3 特殊值1.4 简写属性1.5 CSS 渐进式增强技术1.6 本章小结 第二章 相对单位(已完结) 2.1 相对…...

ZYNQ LWIP(RAW API) TCP函数学习
1 LWIP TCP函数学习 tcp_new()–新建控制块 这个函数用于分配一个TCP控制块,它通过tcp_alloc()函数分配一个TCP控制块结构来存储TCP控制块的数据信息, 如果没有足够的内容分配空间,那么tcp_alloc()函数就会尝试释放一些不太重要的TCP控制块, 比如就会释放处于TIME_WAIT、C…...

Spring Boot,在应用程序启动后执行某些 SQL 语句
在 Spring Boot 中,如果你想在应用程序启动后执行某些 SQL 语句,可以利用 spring.sql.init 属性来配置初始化脚本。这通常用于在应用启动时创建数据库表、索引、视图等,或者填充默认数据。data-locations 和 schema-locations 指定了 SQL 脚本…...

【SQL】百题计划:SQL最基本的判断和查询。
[SQL]百题计划 Select product_id from Products where low_fats "Y" and recyclable "Y";...

04_Python数据类型_列表
Python的基础数据类型 数值类型:整数、浮点数、复数、布尔字符串容器类型:列表、元祖、字典、集合 列表 列表(List)是一种非常灵活的数据类型,它可以用来存储一系列的元素。容器类型,能够存储多个元素的…...

F5设备绑定EIP
公网IP 公网IP(Public IP Address)是指可以直接在互联网上访问的IP地址,用于标识网络上的设备或主机。它允许外部网络访问您的云服务器,如提供远程登录服务、访问Web服务器等。然而,这并不意味着公网IP不需要路由。 …...