C++11的互斥包装器
文章目录
- 1. 为何要引入互斥包装器?
- 2. lock_guard
- 3. unique_lock
- 4. 两者之间的不同
- 5. 总结
1. 为何要引入互斥包装器?
在C++多线程中会经常用到mutex,在使用的时候lock后,有时候会忘记使用unlock进行解锁造成死锁,或者在lock和unlock之间代码异常跳出,导致程序无法执行到unlock造成死锁,因此在C++11中引入互斥体包装器,互斥体包装器为互斥提供了便利的RAII风格机制,本质上就是在包装器的构造函数中加锁,在析构函数中解锁,将加锁和解锁操作与对象的生存期深度绑定,防止使用mutex加锁(lock)后,忘记解锁(unlock)或者两者之间出现异常退出等造成死锁。
RAII(Resource Acquisition Is Initialization, 资源获取即初始化)
RAII是一种 C++ 编程技术 ,它将必须在使用前请求的资源(分配的堆内存、执行线程、打开的套接字、打开的文件、锁定的互斥体、磁盘空间、数据库连接等——任何存在受限供给中的事物)的生命周期与一个对象的生存期相绑定。RAII 保证资源能够用于任何会访问该对象的函数(资源可用性是一种类不变式,这会消除冗余的运行时测试)。它也保证对象在自己生存期结束时会以获取顺序的逆序释放它控制的所有资源。
C++11提供了lock_guard和unique_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::swap | std::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_lock对g_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_for、try_lock_until等带时间的加锁方法,以及其他的特殊方法,我们可以根据不同的应用场景选择合适的方法。
5. 总结
unique_lock与lock_guard最大的区别在于unique_lock提供了手动解锁的方法,增加了中途解锁的功能,而不是像lock_guard必须等待对象析构时解锁,增加了控锁数据的精细程度,提高程序的效率。
同时unique_lock还提供了更多的公有方法供我们按需使用。但是,方便肯定是有代价的,unique_lock在增加这些新方法的同时,方法内部也增加一些新的逻辑和资源占用,例如unlock功能,其内部需要维护一个锁的状态,所以整体在效率上会比lock_guard差一点。因此对于普通的简单场景,lock_guard也是不错的选择。
文章首发公众号:iDoitnow如果喜欢话,可以关注一下
相关文章:
C++11的互斥包装器
文章目录 1. 为何要引入互斥包装器?2. lock_guard3. unique_lock4. 两者之间的不同5. 总结 1. 为何要引入互斥包装器? 在C多线程中会经常用到mutex,在使用的时候lock后,有时候会忘记使用unlock进行解锁造成死锁,或者在…...
HR应用在线人才测评,给企业招聘带来的好处
一、什么是人才测评? 人才测评是指运用一系列的科学方法,对人的基本素质,专业能力,心理健康,性格进行选拔,评价及发展人才的一种科学方法。近十多年,它被广泛运用于国有大型企业的人才招聘和人…...
深入了解百度爬虫工作原理
在当今数字化时代,互联网已经成为人们获取信息的主要渠道之一。而搜索引擎作为互联网上最重要的工具之一,扮演着连接用户与海量信息的桥梁角色。然而,我们是否曾经好奇过当我们在搜索引擎中输入关键词并点击搜索按钮后,究竟是如何…...
【C语言基础】分享近期学习到的volatile关键字、__NOP__()函数以及# #if 1 #endif
📢:如果你也对机器人、人工智能感兴趣,看来我们志同道合✨ 📢:不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 📢:文章若有幸对你有帮助,可点赞 👍…...
docker容器自启动
场景 当服务器关机重启后,docker容器每次都要去docker start 容器id 怎么可以下次让它自启动呢? 解决 先 # docker ps -a 查到之前启动过的容器id # docker update --restartalways 容器id重启后,reboot,就不用再单独去启动容…...
【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应用中,限流通常用于控制接口请求的频率,防止过多的请求导致系统负载过大或者防止恶意攻击。 什么是限流? 限流是一种通过…...
【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报文,由用户指定servername构造对应的报文。网上对于此的资料甚少,有的也是怎么去解析https报文,但是对于如果构造基本上没有找到相关的资料。 一直觉得最好的老师就是Python的help功能和dir功…...
程序设计实践学习笔记
第1题 题目描述 创建一个返回四舍五入到最接近整数的分数之和的函数。在矩阵中有每行的第一个数字表示分子,第二个数子表示分母,挑战者需要将该分数的结果进行四舍五入并将矩阵中所有分数结果总和进行返回。 输入输出格式 输入格式 数字 N 表示的是矩阵的行数。…...
Ubuntu中apt-get update显示域名解析失败
第一步 检查主机->虚拟机能否ping成功 ping 红色框中的IPv4地址 能通,表示虚拟机ip配置成功;否则,需要先配置虚拟机ip 第二步 检查是否能ping成功百度网址 ping www.baidu.com 若不成功,可能原因 虚拟机没联网,打开火狐浏览器…...
go学习之简单项目
项目 文章目录 项目1.项目开发流程图2.家庭收支记账软件项目2)项目代码实现3)具体功能实现 3.客户信息管理系统1)项目需求说明2)界面设计3)项目框架图4)流程5)完成显示客户列表的功能6ÿ…...
代码随想录二刷 | 数组 | 总结篇
代码随想录二刷 | 数组 | 总结篇 基础知识二分查找移除元素有序数组的平方长度最小的数组最小覆盖子串螺旋数组 基础知识 定义:数组是存放在连续内存空间上的相同类型数据的集合 特点: 数组下标从 0 开始数组内存空间的地址是连…...
go test 命令详解
文章目录 1.简介2.test flag3.test/binary flags4.常用选项5.示例参考文献 1.简介 go test 是 Go 用来执行测试函数(test function)、基准函数(benchmark function)和示例函数(example function)的命令。 …...
【Mysql学习笔记】1 - Mysql入门
一、Mysql5.7安装配置 下载后会得到zip 安装文件解压的路径最好不要有中文和空格这里我解压到 D:\hspmysql\mysql-5.7.19-winx64 目录下 【根据自己的情况来指定目录,尽量选择空间大的盘】 添加环境变量 : 电脑-属性-高级系统设置-环境变量,在Path 环境变量增加mysq…...
sentinel 网关
网关简介 大家都都知道在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端要如何去调用这么多的微服务呢?如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去调用。 这样的架构,会存在…...
常见面试题-MySQL的Explain执行计划
了解 Explain 执行计划吗? 答: explain 语句可以帮助我们查看查询语句的具体执行计划。 explain 查出来的各列含义如下: id:在一个大的查询语句中,每个 select 关键字都对应一个唯一的 id select_type:…...
SpringBoot静态资源配置
项目中 SSM中配置 第一种:配置文件中 <mvc:resources mapping"/js/**" location"/js/"/> <mvc:resources mapping"/css/**" location"/css/"/> <mvc:resources mapping"/html/**" location&q…...
Java拼图
第一步是创建项目 项目名自拟 第二部创建个包名 来规范class 然后是创建类 创建一个代码类 和一个运行类 代码如下: package heima;import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import jav…...
Linux 怎样通过win 远程桌面连接链接Linux后台服务器的可视化图形界面
目的概述:因不想后台直接操作(操作不便),所以想到能否基于xrdp协议服务利用 win自带的远程桌面服务,链接到后台,类似于vnc的使用方式,涉及操作系统版本:win11 、 CentOS 7.4 、CentO…...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...
智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)
概述 在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过,在涉及到多个子类派生于基类进行多态模拟的场景下,…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...
Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...
网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
