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

C++11的互斥包装器

文章目录

    • 1. 为何要引入互斥包装器?
    • 2. lock_guard
    • 3. unique_lock
    • 4. 两者之间的不同
    • 5. 总结

1. 为何要引入互斥包装器?

在C++多线程中会经常用到mutex,在使用的时候lock后,有时候会忘记使用unlock进行解锁造成死锁,或者在lockunlock之间代码异常跳出,导致程序无法执行到unlock造成死锁,因此在C++11中引入互斥体包装器,互斥体包装器为互斥提供了便利的RAII风格机制,本质上就是在包装器的构造函数中加锁,在析构函数中解锁,将加锁和解锁操作与对象的生存期深度绑定,防止使用mutex加锁(lock)后,忘记解锁(unlock)或者两者之间出现异常退出等造成死锁。

RAII(Resource Acquisition Is Initialization, 资源获取即初始化)

RAII是一种 C++ 编程技术 ,它将必须在使用前请求的资源(分配的堆内存、执行线程、打开的套接字、打开的文件、锁定的互斥体、磁盘空间、数据库连接等——任何存在受限供给中的事物)的生命周期与一个对象的生存期相绑定。RAII 保证资源能够用于任何会访问该对象的函数(资源可用性是一种类不变式,这会消除冗余的运行时测试)。它也保证对象在自己生存期结束时会以获取顺序的逆序释放它控制的所有资源。

C++11提供了lock_guardunique_lock两种互斥包装器。

2. lock_guard

lock_guard 是互斥体包装器,为在作用域块期间占有互斥提供便利RAII风格机制。其在头文件<mutex>中定义,其函数原型如下:

template< class Mutex >
class lock_guard;

其构造函数如下:

//等效地调用 m.lock() 
explicit lock_guard( mutex_type& m );  //C++11 起//获得互斥 m 的所有权而不试图锁定它。若当前线程不在 m 上保有非共享锁
//(即由 lock、 try_lock、 try_lock_for 或 try_lock_until 
//取得的锁)则行为未定义。
lock_guard( mutex_type& m, std::adopt_lock_t t ); //C++11 起//复制构造函数被删除
lock_guard( const lock_guard& ) = delete; //C++11 起

析构函数如下:

//释放所占有互斥的所有权。
//等效地调用 m.unlock() ,
//其中 m 是传递个 lock_guard 的构造函数的互斥
~lock_guard(); //C++11 起

创建 lock_guard 对象时,它试图接收给定互斥的所有权。控制离开创建 lock_guard 对象的作用域时,销毁 lock_guard 并释放互斥。lock_guard 类不可复制。

:若 m 先于 lock_guard 对象被销毁,则行为未定义。

示例:

#include <thread>
#include <mutex>
#include <iostream>int g_i = 0;
std::mutex g_i_mutex;  // 保护 g_ivoid safe_increment()
{std::lock_guard<std::mutex> lock(g_i_mutex);++g_i;std::cout << std::this_thread::get_id() << ": " << g_i << '\n';// g_i_mutex 在锁离开作用域时自动释放
}int main()
{std::cout << "main: " << g_i << '\n';std::thread t1(safe_increment);std::thread t2(safe_increment);t1.join();t2.join();std::cout << "main: " << g_i << '\n';
}

可能的输出:

main: 0
140641306900224: 1
140641298507520: 2
main: 2

3. unique_lock

unique_lock也是C++11提供的一种通用互斥包装器,它允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。其也在头文件 <mutex>中定义,其构造函数如下:

//构造无关联互斥的 unique_lock 
unique_lock() noexcept; //C++11 起//移动构造函数。以 other 的内容初始化 unique_lock 。令 other 无关联互斥
unique_lock( unique_lock&& other ) noexcept; //C++11 起// 构造以 m 为关联互斥的 unique_lock
// 通过调用 m.lock() 锁定关联互斥
explicit unique_lock( mutex_type& m ); //C++11 起// 构造以 m 为关联互斥的 unique_lock
// 不锁定关联互斥
unique_lock( mutex_type& m, std::defer_lock_t t ) noexcept; //C++11 起// 构造以 m 为关联互斥的 unique_lock
// 通过调用 m.try_lock() 尝试锁定关联互斥而不阻塞。
// 若 Mutex 不满足可锁定 (Lockable) 则行为未定义
unique_lock( mutex_type& m, std::try_to_lock_t t ); //C++11 起// 构造以 m 为关联互斥的 unique_lock 
// 假定调用方线程已保有 m 上的非共享锁(即由 lock、 try_lock、 try_lock_for 
// 或 try_lock_until 取得的锁)。若非如此则行为未定义
unique_lock( mutex_type& m, std::adopt_lock_t t ); //C++11 起// 构造以 m 为关联互斥的 unique_lock 
// 通过调用 m.try_lock_for(timeout_duration) 尝试锁定关联互斥。
// 阻塞到经过指定的 timeout_duration 或获得锁这两个事件的先到来者为止
template< class Rep, class Period >
unique_lock( mutex_type& m,const std::chrono::duration<Rep,Period>& timeout_duration ); //C++11 起// 构造以 m 为关联互斥的 unique_lock
// 通过调用 m.try_lock_until(timeout_time) 尝试锁定关联互斥。
// 阻塞到抵达指定的 timeout_time 或获得锁这两个事件的先到来者为止
template< class Clock, class Duration >
unique_lock( mutex_type& m,const std::chrono::time_point<Clock,Duration>& timeout_time ); //C++11 起

unique_lock除了提供 lock_guard有的基础功能外,还提供了锁定等相关的方法,使得其更加灵活方便,其提供的方法有:

函数说明备注
lock锁定关联互斥公开成员函数
try_lock尝试锁定关联互斥,若互斥不可用则返回公开成员函数
try_lock_for试图锁定关联的定时可锁互斥,若互斥在给定时长中不可用则返回公开成员函数
try_lock_until尝试锁定关联可定时锁互斥,若抵达指定时间点互斥仍不可用则返回公开成员函数
unlock解锁关联互斥公开成员函数
swap与另一std::unique_lock 交换状态公开成员函数
release将关联互斥解关联而不解锁它公开成员函数
mutex返回指向关联互斥的指针公开成员函数
own_lock测试锁是否占有其关联互斥公开成员函数
operator bool测试锁是否占有其关联互斥公开成员函数
std::swapstd::swap对 unique_lock 的特化,功能与其成员函数swap类似非成员函数

示例:

#include <mutex>
#include <thread>
#include <iostream>
#include <vector>
#include <chrono>int main()
{int counter = 0;std::mutex counter_mutex;std::vector<std::thread> threads;auto worker_task = [&](int id) {std::unique_lock<std::mutex> lock(counter_mutex);++counter;std::cout << id << ", initial counter: " << counter << '\n';lock.unlock();// 我们模拟昂贵操作时不保有锁std::this_thread::sleep_for(std::chrono::seconds(1));lock.lock();++counter;std::cout << id << ", final counter: " << counter << '\n';};for (int i = 0; i < 10; ++i) threads.emplace_back(worker_task, i);for (auto &thread : threads) thread.join();
}

可能的输出:

0, initial counter: 1
1, initial counter: 2
2, initial counter: 3
3, initial counter: 4
4, initial counter: 5
5, initial counter: 6
6, initial counter: 7
7, initial counter: 8
8, initial counter: 9
9, initial counter: 10
6, final counter: 11
3, final counter: 12
4, final counter: 13
2, final counter: 14
5, final counter: 15
0, final counter: 16
1, final counter: 17
7, final counter: 18
9, final counter: 19
8, final counter: 20

4. 两者之间的不同

lock_guard的使用方法非常简单,通过构造函数上锁,在销毁的时候解锁,对于一些简单的场景使用也非常方便高效,但对于一些作用域比较大的场景,可能会影响效率,例如如下场景:

int g_i = 0;
std::mutex g_i_mutex;  // 保护 g_ivoid safe_increment()
{std::lock_guard<std::mutex> lock(g_i_mutex);++g_i;std::cout << std::this_thread::get_id() << ": " << g_i << '\n';//流程1开始...//流程1结束// g_i_mutex 在锁离开作用域时自动释放
}

如上例所述,如果流程1的过程特别长,而且不涉及g_i的操作,如果使用lock_guard的话会导致g_i上锁时间特别长,影响其他线程的对其所有权的获取,影响整个代码的运行效率。因此,针对这种应用场景,我们应该使用unique_lockg_i进行互斥锁管理,我们可以在流程1的开始处,进行手动解锁,提前释放g_i的所有权,提高程序的效率。

int g_i = 0;
std::mutex g_i_mutex;  // 保护 g_ivoid safe_increment()
{std::unique_lock<std::mutex> lock(g_i_mutex);++g_i;std::cout << std::this_thread::get_id() << ": " << g_i << '\n';lock.unlock(); //提前释放g_i的所有权//流程处理1开始....//流程处理1结束// g_i_mutex 在锁离开作用域时检测到已经unlock了,就不会再次调用unlock
}

:对于上面的例子,lock_guard也可以通过{ }来控制lock_guard对象的作用域,进而将控锁的范围进一步缩小。

unique_lock除了提供可以手动解锁的方法外,还额外提供了try_lock_fortry_lock_until等带时间的加锁方法,以及其他的特殊方法,我们可以根据不同的应用场景选择合适的方法。

5. 总结

unique_locklock_guard最大的区别在于unique_lock提供了手动解锁的方法,增加了中途解锁的功能,而不是像lock_guard必须等待对象析构时解锁,增加了控锁数据的精细程度,提高程序的效率。

同时unique_lock还提供了更多的公有方法供我们按需使用。但是,方便肯定是有代价的,unique_lock在增加这些新方法的同时,方法内部也增加一些新的逻辑和资源占用,例如unlock功能,其内部需要维护一个锁的状态,所以整体在效率上会比lock_guard差一点。因此对于普通的简单场景,lock_guard也是不错的选择。

文章首发公众号:iDoitnow如果喜欢话,可以关注一下

相关文章:

C++11的互斥包装器

文章目录 1. 为何要引入互斥包装器&#xff1f;2. lock_guard3. unique_lock4. 两者之间的不同5. 总结 1. 为何要引入互斥包装器&#xff1f; 在C多线程中会经常用到mutex&#xff0c;在使用的时候lock后&#xff0c;有时候会忘记使用unlock进行解锁造成死锁&#xff0c;或者在…...

HR应用在线人才测评,给企业招聘带来的好处

一、什么是人才测评&#xff1f; 人才测评是指运用一系列的科学方法&#xff0c;对人的基本素质&#xff0c;专业能力&#xff0c;心理健康&#xff0c;性格进行选拔&#xff0c;评价及发展人才的一种科学方法。近十多年&#xff0c;它被广泛运用于国有大型企业的人才招聘和人…...

深入了解百度爬虫工作原理

在当今数字化时代&#xff0c;互联网已经成为人们获取信息的主要渠道之一。而搜索引擎作为互联网上最重要的工具之一&#xff0c;扮演着连接用户与海量信息的桥梁角色。然而&#xff0c;我们是否曾经好奇过当我们在搜索引擎中输入关键词并点击搜索按钮后&#xff0c;究竟是如何…...

【C语言基础】分享近期学习到的volatile关键字、__NOP__()函数以及# #if 1 #endif

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…...

docker容器自启动

场景 当服务器关机重启后&#xff0c;docker容器每次都要去docker start 容器id 怎么可以下次让它自启动呢&#xff1f; 解决 先 # docker ps -a 查到之前启动过的容器id # docker update --restartalways 容器id重启后&#xff0c;reboot&#xff0c;就不用再单独去启动容…...

【C++】:模板的使用

目录 1、泛型编程 2、函数模板 2.1、函数模板概念 2.2、函数模板格式 2.3、函数模板的原理 2.4、函数模板的实例化 2.6、模板参数的匹配原则 3、类模板 3.1、 类模板的定义格式 3.2、 类模板的实例化 4、非类型模板参数 5、模板的特化 5.1、函数模板特化 5.2、类模…...

Springboot框架中使用 Redis + Lua 脚本进行限流功能

Springboot框架中使用 Redis Lua 脚本进行限流功能 限流是一种用于控制系统资源利用率或确保服务质量的策略。在Web应用中&#xff0c;限流通常用于控制接口请求的频率&#xff0c;防止过多的请求导致系统负载过大或者防止恶意攻击。 什么是限流&#xff1f; 限流是一种通过…...

【nlp】2.5(cpu version) 人名分类器实战项目(对比RNN、LSTM、GRU模型)

人名分类器实战项目 0 项目说明1 案例介绍2 案例步骤2.1 导入必备的工具包2.2 数据预处理2.2.1 获取常用的字符数量2.2.2 国家名种类数和个数2.2.3 读数据到python环境中2.2.4 构建数据源NameClassDataset2.2.5 构建迭代器遍历数据2.3 构建RNN及其变体模型2.3.1 构建RNN模型2.3…...

记录基于scapy构造ClientHello报文的尝试

最近有个需求就是用scapy构造https的client hello报文&#xff0c;由用户指定servername构造对应的报文。网上对于此的资料甚少&#xff0c;有的也是怎么去解析https报文&#xff0c;但是对于如果构造基本上没有找到相关的资料。 一直觉得最好的老师就是Python的help功能和dir功…...

程序设计实践学习笔记

第1题 题目描述 创建一个返回四舍五入到最接近整数的分数之和的函数。在矩阵中有每行的第一个数字表示分子&#xff0c;第二个数子表示分母,挑战者需要将该分数的结果进行四舍五入并将矩阵中所有分数结果总和进行返回。 输入输出格式 输入格式 数字 N 表示的是矩阵的行数。…...

Ubuntu中apt-get update显示域名解析失败

第一步 检查主机->虚拟机能否ping成功 ping 红色框中的IPv4地址 能通&#xff0c;表示虚拟机ip配置成功;否则&#xff0c;需要先配置虚拟机ip 第二步 检查是否能ping成功百度网址 ping www.baidu.com 若不成功&#xff0c;可能原因 虚拟机没联网&#xff0c;打开火狐浏览器…...

go学习之简单项目

项目 文章目录 项目1.项目开发流程图2.家庭收支记账软件项目2&#xff09;项目代码实现3&#xff09;具体功能实现 3.客户信息管理系统1&#xff09;项目需求说明2&#xff09;界面设计3&#xff09;项目框架图4&#xff09;流程5&#xff09;完成显示客户列表的功能6&#xff…...

代码随想录二刷 | 数组 | 总结篇

代码随想录二刷 &#xff5c; 数组 &#xff5c; 总结篇 基础知识二分查找移除元素有序数组的平方长度最小的数组最小覆盖子串螺旋数组 基础知识 定义&#xff1a;数组是存放在连续内存空间上的相同类型数据的集合 特点&#xff1a; 数组下标从 0 开始数组内存空间的地址是连…...

go test 命令详解

文章目录 1.简介2.test flag3.test/binary flags4.常用选项5.示例参考文献 1.简介 go test 是 Go 用来执行测试函数&#xff08;test function&#xff09;、基准函数&#xff08;benchmark function&#xff09;和示例函数&#xff08;example function&#xff09;的命令。 …...

【Mysql学习笔记】1 - Mysql入门

一、Mysql5.7安装配置 下载后会得到zip 安装文件解压的路径最好不要有中文和空格这里我解压到 D:\hspmysql\mysql-5.7.19-winx64 目录下 【根据自己的情况来指定目录,尽量选择空间大的盘】 添加环境变量 : 电脑-属性-高级系统设置-环境变量&#xff0c;在Path 环境变量增加mysq…...

sentinel 网关

网关简介 大家都都知道在微服务架构中&#xff0c;一个系统会被拆分为很多个微服务。那么作为客户端要如何去调用这么多的微服务呢&#xff1f;如果没有网关的存在&#xff0c;我们只能在客户端记录每个微服务的地址&#xff0c;然后分别去调用。 这样的架构&#xff0c;会存在…...

常见面试题-MySQL的Explain执行计划

了解 Explain 执行计划吗&#xff1f; 答&#xff1a; explain 语句可以帮助我们查看查询语句的具体执行计划。 explain 查出来的各列含义如下&#xff1a; id&#xff1a;在一个大的查询语句中&#xff0c;每个 select 关键字都对应一个唯一的 id select_type&#xff1a;…...

SpringBoot静态资源配置

项目中 SSM中配置 第一种&#xff1a;配置文件中 <mvc:resources mapping"/js/**" location"/js/"/> <mvc:resources mapping"/css/**" location"/css/"/> <mvc:resources mapping"/html/**" location&q…...

Java拼图

第一步是创建项目 项目名自拟 第二部创建个包名 来规范class 然后是创建类 创建一个代码类 和一个运行类 代码如下&#xff1a; package heima;import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import jav…...

Linux 怎样通过win 远程桌面连接链接Linux后台服务器的可视化图形界面

目的概述&#xff1a;因不想后台直接操作&#xff08;操作不便&#xff09;&#xff0c;所以想到能否基于xrdp协议服务利用 win自带的远程桌面服务&#xff0c;链接到后台&#xff0c;类似于vnc的使用方式&#xff0c;涉及操作系统版本&#xff1a;win11 、 CentOS 7.4 、CentO…...

【JavaEE】-- HTTP

1. HTTP是什么&#xff1f; HTTP&#xff08;全称为"超文本传输协议"&#xff09;是一种应用非常广泛的应用层协议&#xff0c;HTTP是基于TCP协议的一种应用层协议。 应用层协议&#xff1a;是计算机网络协议栈中最高层的协议&#xff0c;它定义了运行在不同主机上…...

边缘计算医疗风险自查APP开发方案

核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

spring:实例工厂方法获取bean

spring处理使用静态工厂方法获取bean实例&#xff0c;也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下&#xff1a; 定义实例工厂类&#xff08;Java代码&#xff09;&#xff0c;定义实例工厂&#xff08;xml&#xff09;&#xff0c;定义调用实例工厂&#xff…...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

ios苹果系统,js 滑动屏幕、锚定无效

现象&#xff1a;window.addEventListener监听touch无效&#xff0c;划不动屏幕&#xff0c;但是代码逻辑都有执行到。 scrollIntoView也无效。 原因&#xff1a;这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作&#xff0c;从而会影响…...

NPOI操作EXCEL文件 ——CAD C# 二次开发

缺点:dll.版本容易加载错误。CAD加载插件时&#xff0c;没有加载所有类库。插件运行过程中用到某个类库&#xff0c;会从CAD的安装目录找&#xff0c;找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库&#xff0c;就用插件程序加载进…...

TSN交换机正在重构工业网络,PROFINET和EtherCAT会被取代吗?

在工业自动化持续演进的今天&#xff0c;通信网络的角色正变得愈发关键。 2025年6月6日&#xff0c;为期三天的华南国际工业博览会在深圳国际会展中心&#xff08;宝安&#xff09;圆满落幕。作为国内工业通信领域的技术型企业&#xff0c;光路科技&#xff08;Fiberroad&…...

从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障

关键领域软件测试的"安全密码"&#xff1a;Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力&#xff0c;从金融交易到交通管控&#xff0c;这些关乎国计民生的关键领域…...

医疗AI模型可解释性编程研究:基于SHAP、LIME与Anchor

1 医疗树模型与可解释人工智能基础 医疗领域的人工智能应用正迅速从理论研究转向临床实践,在这一过程中,模型可解释性已成为确保AI系统被医疗专业人员接受和信任的关键因素。基于树模型的集成算法(如RandomForest、XGBoost、LightGBM)因其卓越的预测性能和相对良好的解释性…...

Springboot 高校报修与互助平台小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;高校报修与互助平台小程序被用户普遍使用&#xff0c;为…...