当前位置: 首页 > news >正文

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 的优点

  1. 自动管理资源:无需手动调用释放资源的函数,减少人为错误。
  2. 异常安全:即使在异常发生时,析构函数也会被调用,确保资源被释放。
  3. 简化代码:减少显式的资源管理代码,使代码更加简洁和易读。

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;
}

执行流程

  1. 创建 lock_guard 对象

    • lock 对象被创建时,调用 mtx.lock(),获取锁。
  2. 执行临界区代码

    • 输出线程 ID,确保输出操作是线程安全的,不会被其他线程打断。
  3. 析构 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 原则,利用构造和析构函数自动管理锁的获取和释放,确保了线程安全性并简化了代码。其主要优势包括:

  1. 自动锁管理:减少手动锁定和释放的错误风险。
  2. 异常安全:即使在异常情况下,锁也能被正确释放。
  3. 代码简洁:使用 lock_guard 可以让代码更加清晰和易维护。

通过遵循 RAII 原则,std::lock_guard 为多线程编程提供了一种高效、安全且易于使用的锁管理方式,是现代 C++ 中推荐的锁管理工具。

 

相关文章:

RAII 与 std::lock_guard 在 C++ 中的应用:自动化互斥锁管理与线程安全

目录 1. RAII&#xff08;资源获取即初始化&#xff09;概述 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设计中&#xff0c;奢华风格通常指的是一种高端、豪华、精致的设计风格&#xff0c;旨在营造出奢华、豪华的视觉效果&#xff0c;给用户带来高品质、高档次的感受。 奢华风格的UI设计通常会运用一些富丽堂皇的元素和效果&#xff0c;例如金色、银色、贵族紫、华丽的字体、华…...

Vue2 qrcode+html2canvas 实现二维码的生成和保存

1.安装 npm install qrcode npm install html2canvas 2.引用 import QRCode from qrcode import html2canvas from html2canvas 效果&#xff1a; 1. 二维码生成&#xff1a; 下载二维码图片&#xff1a; 二维码的内容&#xff1a; 实现代码&#xff1a; <template>…...

GEE 教程:利用Google Dynamic数据进行逐月指定区域的土地分类数据提取分析

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

Nginx 负载均衡:优化网站性能与可扩展性的利器

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

【Python基础】Python错误和异常处理(详细实例)

本文收录于 《Python编程入门》专栏&#xff0c;从零基础开始&#xff0c;分享一些Python编程基础知识&#xff0c;欢迎关注&#xff0c;谢谢&#xff01; 文章目录 一、前言二、Python中的错误类型三、Python异常处理机制3.1 try-except语句3.2 try-except-else语句3.3 try-fi…...

如何查看串口被哪个程序占用?截止目前最方便的方法

痛点&#xff1a;串口因为某种原因被占用&#xff0c;如何找到罪魁祸首&#xff1f; 做开发的小伙伴们&#xff0c;经常会遇到这样的问题&#xff1a;串口因为某种原因被占用&#xff0c;导致无法通讯&#xff0c;但是又找不到被哪个程序占用。只有重启电脑&#xff0c;才能解…...

深入理解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)&#xff1a;建立在关系模型基础上&#xff0c;由多张相互连接的二维表组成的数据库。 特点&#xff1a; 使用表存储数据&#xff0c;格式统一&#xff0c;便于维护 使用SQL语言操作&#xff0c;标准统一&…...

【STM32系统】基于STM32设计的SD卡数据读取与上位机显示系统(SDIO接口驱动、雷龙SD卡)——文末资料下载

基于STM32设计的SD卡数据读取与上位机显示系统 演示视频&#xff1a; 基于STM32设计的SD卡数据读取与上位机显示系统 简介&#xff1a;本研究的主要目的是基于STM32F103微控制器&#xff0c;设计一个能够读取SD卡数据并显示到上位机的系统。SD卡的数据扇区读取不仅是为了验证存…...

SpringBoot开发——整合Redis

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

OpenCV结构分析与形状描述符(17)判断轮廓是否为凸多边形的函数isContourConvex()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 测试轮廓的凸性。 该函数测试输入的轮廓是否为凸的。轮廓必须是简单的&#xff0c;即没有自相交。否则&#xff0c;函数的输出是不确定的。 cv:…...

P5425 [USACO19OPEN] I Would Walk 500 Miles G

*原题链接* 很离谱的题。首先可以想到暴力连边&#xff0c;整个图为一个完全图&#xff0c;将所有的边选出来&#xff0c;然后从小到大一条条加入&#xff0c;当剩下集合数量 <K 的时候就结束。答案为加入的最后一条边的大小。如果用prim算法的话时间复杂度为。足以通过此题…...

Java高级Day41-反射入门

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

在Linux系统上使用Docker部署java项目

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

【C++】标准库IO查漏补缺

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

python简单易懂的lxml读取HTML节点及常用操作方法

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

Java | Leetcode Java题解之第406题根据身高重建队列

题目&#xff1a; 题解&#xff1a; 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备案等场景&#xff0c;需要app的 证书MD5指纹和公钥&#xff0c;示例如下&#xff1a; 步骤1&#xff1a;使用keytool从APK中提取证书 1. 打开命令行&#xff0c;cd 到你的apk目录&#xff0c;如&#xff1a;app/release 2. 解压APK文件&#xff1a; unzip yo…...

【leetcode_python】杨辉三角

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

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

Linux简单的操作

ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...

cf2117E

原题链接&#xff1a;https://codeforces.com/contest/2117/problem/E 题目背景&#xff1a; 给定两个数组a,b&#xff0c;可以执行多次以下操作&#xff1a;选择 i (1 < i < n - 1)&#xff0c;并设置 或&#xff0c;也可以在执行上述操作前执行一次删除任意 和 。求…...

【2025年】解决Burpsuite抓不到https包的问题

环境&#xff1a;windows11 burpsuite:2025.5 在抓取https网站时&#xff0c;burpsuite抓取不到https数据包&#xff0c;只显示&#xff1a; 解决该问题只需如下三个步骤&#xff1a; 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

Mac下Android Studio扫描根目录卡死问题记录

环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中&#xff0c;提示一个依赖外部头文件的cpp源文件需要同步&#xff0c;点…...

Java编程之桥接模式

定义 桥接模式&#xff08;Bridge Pattern&#xff09;属于结构型设计模式&#xff0c;它的核心意图是将抽象部分与实现部分分离&#xff0c;使它们可以独立地变化。这种模式通过组合关系来替代继承关系&#xff0c;从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...

uniapp手机号一键登录保姆级教程(包含前端和后端)

目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号&#xff08;第三种&#xff09;后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...

关于easyexcel动态下拉选问题处理

前些日子突然碰到一个问题&#xff0c;说是客户的导入文件模版想支持部分导入内容的下拉选&#xff0c;于是我就找了easyexcel官网寻找解决方案&#xff0c;并没有找到合适的方案&#xff0c;没办法只能自己动手并分享出来&#xff0c;针对Java生成Excel下拉菜单时因选项过多导…...