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++ 中推荐的锁管理工具。

参考:

0voice · GitHub

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

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

相关文章

Java免税购物商城:Spring Boot技术实现

第二章 系统开发关键技术 2.1 JAVA技术 Java主要采用CORBA技术和安全模型&#xff0c;可以在互联网应用的数据保护。它还提供了对EJB&#xff08;Enterrise JavaBeans&#xff09;的全面支持&#xff0c;java servlet AI&#xff0c;JS&#xff08;java server ages&#xff09…

RNN的反向传播

目录 1.RNN网络&#xff1a;通过时间反向传播(through time back propagate TTBP) 2.RNN梯度分析 2.1隐藏状态和输出 2.2正向传播&#xff1a; 2.3反向传播&#xff1a; 2.4问题瓶颈&#xff1a; 3.截断时间步分类&#xff1a; 4.截断策略比较 5.反向传播的细节 ​编辑…

植物病害识别系统Python+卷积神经网络算法+图像识别+人工智能项目+深度学习项目+计算机课设项目+Django网页界面

一、介绍 植物病害识别系统。本系统使用Python作为主要编程语言&#xff0c;通过收集水稻常见的四种叶片病害图片&#xff08;‘细菌性叶枯病’, ‘稻瘟病’, ‘褐斑病’, ‘稻瘟条纹病毒病’&#xff09;作为后面模型训练用到的数据集。然后使用TensorFlow搭建卷积神经网络算…

【后端开发】JavaEE初阶—线程的理解和编程实现

前言&#xff1a; &#x1f31f;&#x1f31f;本期讲解多线程的知识哟~~~&#xff0c;希望能帮到屏幕前的你。 &#x1f308;上期博客在这里&#xff1a;【后端开发】JavaEE初阶——计算机是如何工作的&#xff1f;&#xff1f;&#xff1f;-CSDN博客 &#x1f308;感兴趣的小伙…

Tomcat中BIO和NIO的区别(Tomcat)

BIO Tomcat中BIO的模型和理论很简单&#xff0c;例图如下 1.Acceptor线程死循环阻塞接收客户端的打过来的socket请求 2.接收到请求之后打包成一个SocketProcessor&#xff08;Runnable&#xff09;&#xff0c;扔到线程池中读取/写入数据 参数配置 1.Acceptor默认线程是1&#…

【Godot4.x】Mesh相关知识总结

概述 很早之前发布过一篇关于几何体程序生成的文章&#xff0c;当时对于三角面和网格的构造其实还没有特别深入的认识&#xff0c;直到自己脑海里想到用二维数组和点更新的方式构造2D类型的多边形Mesh结构&#xff0c;也意识到在Godot中其实Mesh不仅是3D网格&#xff0c;也可以…

分子动力学工具篇二:Sobtop的安装与使用

文章目录 1. Sobtop的介绍1. Sobtop 的功能和特点2.主要应用场景 2.3.常见问题及解决方法 1. Sobtop的介绍 Sobtop 是一个用于自动生成分子拓扑文件的工具&#xff0c;特别是为 GROMACS 分子动力学模拟准备拓扑结构和参数。它的设计目标是通过自动化过程生成小分子、聚合物或其…

msvcp140.dll0丢失的解决方法,总结6种靠谱的解决方法

再使用计算机的过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“msvcp140.dll丢失”。这个问题可能会影响到我们的正常使用&#xff0c;因此需要及时解决。经过一段时间的学习和实践&#xff0c;我总结了以下六种靠谱的解决方法&#xff0c;希望对大家…

Flask-WTF的使用

组织一个 Flask 项目通常需要遵循一定的结构&#xff0c;以便代码清晰、可维护。下面是一个典型的 Flask 项目结构&#xff1a; my_flask_app/ │ ├── app/ │ ├── __init__.py │ ├── models.py │ ├── views.py │ ├── forms.py │ ├── templat…

【HTTP】构造HTTP请求和状态码

状态码 用于响应中&#xff0c;表示响应的结果如何 正确&#xff1f;错误&#xff1f;什么原因&#xff1f; HTTP 中的状态码都是标准约定好的 200 OK 成功了&#xff0c;一切顺利 在抓包到的响应中 404 Not Found 访问的资源&#xff08;URL 中的路径&#xff09;没找…

数据结构之线性表——LeetCode:707. 设计链表,206. 反转链表,92. 反转链表 II

707. 设计链表 题目描述 707. 设计链表 你可以选择使用单链表或者双链表&#xff0c;设计并实现自己的链表。 单链表中的节点应该具备两个属性&#xff1a;val 和 next 。val 是当前节点的值&#xff0c;next 是指向下一个节点的指针/引用。 如果是双向链表&#xff0c;则…

小程序——生命周期

文章目录 运行机制更新机制生命周期介绍应用级别生命周期页面级别生命周期组件生命周期生命周期两个细节补充说明总结 运行机制 用一张图简要概述一下小程序的运行机制 冷启动与热启动&#xff1a; 小程序启动可以分为两种情况&#xff0c;一种是冷启动&#xff0c;一种是热…

低代码门户技术:构建高效应用的全新方式

什么是低代码门户技术&#xff1f; 低代码门户技术是一种利用低代码平台构建企业门户网站或应用的技术。门户通常是企业内部和外部用户访问信息和应用的集中平台。低代码门户技术通过图形化界面和预置组件&#xff0c;允许用户快速搭建和定制这些门户平台&#xff0c;而无需深…

CefSharp_Vue交互(Element UI)_WinFormWeb应用(3)---通过页面锁屏和关机(含示例代码)

一、预览 实现功能:通过vue标题栏按钮锁屏和关机 1.1 预览 1.2 代码 锁屏代码csharp LockWorkStation() 关机代码chsharp 注意vue代码参数和此参数一致(0/1/2) 方法ExitWindowsEx()

mac电脑打不开rar文件怎么办 rar文件怎么转换成zip并打开

rar文件是一种常见的压缩文件格式&#xff0c;它可以将多个文件或文件夹打包成一个文件&#xff0c;从而节省空间和方便传输。但是&#xff0c;mac系统并没有自带的工具可以直接打开或解压rar文件&#xff0c;那么&#xff0c;mac用户该如何处理rar文件呢&#xff1f; 一、mac电…

初步认识C++模版

前言 在C语言中&#xff0c;我们知道函数的形参需要指定类型&#xff0c;但是在C中&#xff0c;我们可以模版实现各种类型参数的通用函数。 1. 泛型编程 我们通过函数重载实现多种类型的同一作用的函数。如交换函数&#xff1a; void Swap(int& left, int& right) …

微信getUserProfile不弹出授权框

当我们在微信小程序开发工具中想要使用getUserProfile来获取个人信息的时候&#xff0c;会发现不弹出授权框&#xff0c;这是什么原因呢&#xff1f; 早在2022年的小程序官方公告中就已经明确给出了小程序用户头像昵称获取规则调整公告 因此如果还想继续使用getUserProfile的弹…

功能测试干了三年,快要废了。。。

8年前刚进入IT行业&#xff0c;到现在学习软件测试的人越来越多&#xff0c;所以在这我想结合自己的一些看法给大家提一些建议。 最近聊到软件测试的行业内卷&#xff0c;越来越多的转行和大学生进入测试行业&#xff0c;导致软件测试已经饱和了&#xff0c;想要获得更好的待遇…

2024华为杯研赛E题保姆级教程思路分析

E题题目&#xff1a;高速公路应急车道紧急启用模型 今年的E题设计到图像/视频处理&#xff0c;实际上&#xff0c;E题的难度相对来说较低&#xff0c;大家不用畏惧视频的处理&#xff0c;被这个吓到。实际上&#xff0c;这个不难&#xff0c;解决了视频的处理问题&#xff0c;…

2024/9/21 408 20题

a b 58-130-180-199-42-15&#xff1a;c d a 184-182-187-176-19941 c d a a c b d c a c b c c c