C++ STL map迭代器失效问题

最近在开发过程中,定位一个问题的时候,发现多线程场景下大量创建和销毁某个C:\Windows\System32\reg.exe时出现了383个进程创建消息处理的接口,和384个进程销毁处理消息的接口都在等待锁,另外一个线程也在等锁,后面看了一下在处理进程创建和进程销毁的IPC消息处理所在类中有三把锁,执行流程都锁住了,猜测应该是某个线程持有锁没释放,导致其他并发线程锁住了,结合转储的dump和log日志,以及使用VS2017加载对应的dump,对并行堆栈中的线程进行分析,找了很久没发现问题。最后想了一下,是不是某个地方线程做了耗时或者同步阻塞操作导致的,或者线程中执行了死循环,排查后发现是因为一个同事在对map做循环遍历时,erase操作不当,导致某个地方迭代器失效,线程崩溃了,持有两把锁,其他所有线程都拿不到锁,导致IPC消息一直无法发送,最后程序无法升级。

为了上述模拟多线程访问死锁的问题,我简单写了个demo示例,在main函数中创建了两个线程,其中一个线程对std::map<std::string, int> g_cityMap数据做删除操作,另外一个线程对std::map<std::string, int> g_cityMap做数据打印操作。
代码如下:

#include <string>
#include <mutex>
#include <thread>
#include <map>
#include <chrono>
#include <iostream>// 共享数据
std::map<std::string, int> g_cityMap = {{"Shanghai", 20000000},{"Wuhan", 13000000},{"Beijing", 17000000},{"Chongqing", 25000000}
};// 共享数据锁
std::mutex g_cityMapMutex;// 线程1的执行函数
// 对g_cityMap做删除操作
void thread_func1()
{std::unique_lock<std::mutex> lk(g_cityMapMutex);for (auto iter = g_cityMap.begin(); iter != g_cityMap.end(); iter++) {if (iter->first == "Chongqing") {g_cityMap.erase(iter);	// 此处g_cityMap对iter执行erase操作后,iter迭代器会失效}}
}// 线程2的执行函数
// 对g_cityMap中的数据进行打印
void thread_func2()
{// 此处先休眠500ms,等待线程1先执行std::this_thread::sleep_for(std::chrono::milliseconds(500));std::unique_lock<std::mutex> lk(g_cityMapMutex);// 打印最终的g_cityMapfor (auto iter : g_cityMap) {std::cout << "[" << iter.first << "," << iter.second << std::endl;}
}int main()
{std::thread thr1(thread_func1);std::thread thr2(thread_func2);if (thr1.joinable()) {thr1.join();}if (thr2.joinable()) {thr2.join();}std::cin.get();return 0;
}

运行上面程序,程序会崩溃
多线程访问map场景下迭代器失效导致线程崩溃
迭代器失效
并行堆栈示意图
线程1在thread_func1函数的第26行执行g_cityMap.erase(iter);操作后,iter迭代器就失效了,导致跳转到for (auto iter = g_cityMap.begin(); iter != g_cityMap.end(); iter++) {这条语句中的iter++操作时,线程1所在线程会崩溃,如下图所示:
线程1的执行堆栈
再来看一下线程2(对应线程ID为7236)的执行堆栈,如下图所示:
线程2的执行堆栈
从上面可以看出,线程7236在代码第37行执行加锁处卡住了,因为g_cityMapMutex被线程19004持有未释放,此时线程7236会被卡住。

map迭代器失效问题

下面来看一下错误的map迭代器失效写法,代码如下:

#include <map>
#include <algorithm>
#include <iostream>
#include <mutex>using std::map;void mapTest()
{std::mutex appPackageInfoMutex;	// 应用map锁std::unique_lock<std::mutex> lk(appPackageInfoMutex);map<int, int> myMap;for (int i = 0; i < 10; i++){myMap.insert(std::make_pair(i, i + 1));}// 打印myMapstd::cout << "Before erase: " << std::endl;for (auto iter : myMap) {std::cout << "[key: " << iter.first << ", value: " << iter.second << " ]" << "\n";}std::cout << std::endl;for (auto iter = myMap.begin(); iter != myMap.end(); iter++){if (iter->first > 5) {myMap.erase(iter);}}// 打印剩余的myMapstd::cout << "After erase: " << std::endl;for (auto iter : myMap) {std::cout << "[key: " << iter.first << ", value: " << iter.second << " ]" << "\n";}std::cout << std::endl;
}int main()
{mapTest();return 0;
}

程序运行结果如下:
map迭代器失效导致程序崩溃
上面程序的意图很明显:就是先往myMap中放置一些键值对的数据:
[0,1],[1,2],[2,3],[3,4],[4,5],[5,6][6,7],[7,8],[8,9],然后在遍历myMap时删除key值大于5的元素。再接着打印操作后的myMap。
迭代器失效导致程序崩溃
map迭代器失效
从上面的错误可以看出:程序报cannot increment value-initialized map/set iterator异常。

正确的map迭代器删除操作示例

正确的写法如下:

#include <map>
#include <algorithm>
#include <iostream>
#include <mutex>using std::map;/******************************************************************************
对于关联容器(如map, set,multimap,multiset),删除当前的iterator,
仅仅会使当前的iterator失效,只要在erase时,递增当前iterator即可。
这是因为map之类的容器,使用了红黑树来实现,插入、删除一个结点不会对其他结点造成影响。
erase迭代器只是被删元素的迭代器失效,但是返回值为void,
所以要采用erase(iter++)的方式删除迭代器。
*******************************************************************************/void mapTest()
{std::mutex appPackageInfoMutex;	// 应用map锁std::unique_lock<std::mutex> lk(appPackageInfoMutex);map<int, int> myMap;for (int i = 0; i < 10; i++){myMap.insert(std::make_pair(i, i + 1));}// 打印myMapstd::cout << "Before erase: " << std::endl;for (auto iter : myMap) {std::cout << "[key: " << iter.first << ", value: " << iter.second << " ]" << "\n";}std::cout << std::endl;for (auto iter = myMap.begin(); iter != myMap.end(); ){if (iter->first > 5) {myMap.erase(iter++);} else {iter++;}}// 打印剩余的myMapstd::cout << "After erase: " << std::endl;for (auto iter : myMap) {std::cout << "[key: " << iter.first << ", value: " << iter.second << " ]" << "\n";}std::cout << std::endl;
}int main()
{mapTest();return 0;
}

运行结果如下图所示:
正确写法运行结果

参考文章

  • 【C++ STL】迭代器失效的几种情况总结
  • STL容器迭代器失效情况分析、总结
  • 迭代器失效的几种情况总结

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

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

相关文章

mysql8下载与安装教程

文章目录 1. MySQL下载2. 方式一&#xff1a;msi文件安装2.1 安装2.2 添加环境变量2.3 登录mysql 3. 方式二&#xff1a;zip文件安装3.1 安装3.2 配置文件3.3 加入环境变量3.4 初始化mysql3.5 登录mysql 1. MySQL下载 以下两个网址二选一 官网&#xff1a;https://downloads.…

CPU、GPU、TPU内存子系统架构

文章目录 CPU、GPU、TPU内存子系统架构概要CPUGPUTPU共同点和差异&#xff1a; CPU、GPU、TPU内存子系统架构 概要 Memory Subsystem Architecture&#xff0c;图源自TVM CPU CPU&#xff08;中央处理器&#xff09;的内存子系统&#xff1a;隐式管理 主内存&#xff08;…

lv11 嵌入式开发 C工程与寄存器封装 10

目录 1 C语言工程介绍 1.1 工程模板介绍 1.2 启动代码分析 2 C语言实现LED实验 2.1 C语言如何实现寄存器读写 2.2 实现LED实验 2.3 练习 1 C语言工程介绍 1.1 工程模板介绍 工程目录&#xff0c;后续代码都会利用到这个目录 interface.c 写了一个main函数的框架 int …

均匀球形分布的随机三维单位向量

生成具有均匀球形分布的随机三维单位向量[参考] import numpy as np import matplotlib.pyplot as plt def random_three_vector():"""Generates a random 3D unit vector (direction) with a uniform spherical distributionAlgo from http://stackoverflow.c…

区间预测 | Matlab实现BP-KDE的BP神经网络结合核密度估计多变量时序区间预测

区间预测 | Matlab实现BP-KDE的BP神经网络结合核密度估计多变量时序区间预测 目录 区间预测 | Matlab实现BP-KDE的BP神经网络结合核密度估计多变量时序区间预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.BP-KDE多变量时间序列区间预测&#xff0c;基于BP神经网络多…

MFC 绘制单一颜色三角形、渐变颜色边框三角形、渐变填充三角形、正弦函数曲线实例

MFC 绘制三种不同圆形以及绘制正弦函数曲线 本文使用visual Studio MFC 平台实现绘制单一颜色圆形、渐变颜色边框圆形、渐变填充圆形以及绘制三角函数正弦函数曲线. 关于基础工程的创建请参考 01-Visual Studio 使用MFC 单文档工程绘制单一颜色直线和绘制渐变颜色的直线 02-vis…

Jenkins CI/CD

1、 Jenkins CI/CD 流程图 说明&#xff1a;这张图稍微更形象一点&#xff0c;上线之前先把代码git到版本仓库&#xff0c;然后通过Jenkins 如Java项目通过maven去构建&#xff0c;这是在非容器之前&#xff0c;典型的自动化的一个版本上线流程。那它有哪些问题呢&#xff1f; …

基于opencv+ImageAI+tensorflow的智能动漫人物识别系统——深度学习算法应用(含python、JS、模型源码)+数据集(四)

目录 前言总体设计系统整体结构图系统流程图 运行环境爬虫模型训练实际应用 模块实现1. 数据准备1&#xff09;爬虫下载原始图片2&#xff09;手动筛选图片 2. 数据处理3. 模型训练及保存4. 模型测试1&#xff09;前端2&#xff09;后端 系统测试1. 测试效果2. 模型应用1&#…

webshell之内置函数免杀

原始webshell 查杀的点在于Runtime.getRuntime().exec非常明显的特征 利用ProcessBuilder替换Runtime.getRuntime().exec(cmd) Runtime.getRuntime().exec(cmd)其实最终调用的是ProcessBuilder这个函数&#xff0c;因此我们可以直接利用ProcessBuilder来替换Runtime.getRunti…

【服务器能干什么】二十分钟搭建一个属于自己的 RSS 服务

如果大家不想自己捣鼓,只是想尝尝鲜,可以在下面留言,我后台帮大家开几个账号玩一玩。 哔哩哔哩【高清版本可以点击去吐槽到 B 站观看】:【VPS服务器到底能干啥】信息爆炸的年代,如何甄别出优质的内容?你可能需要自建一个RSS服务!_哔哩哔哩_bilibili 前言 RSS 服务 市…

二年级 最少需要几个刻度?

娃二年级题目&#xff1a;请你设计一把尺子&#xff0c;用这把尺子一次能画出 1~8厘米八条不同长度的线段。最少需要几个刻度&#xff1f; 答&#xff1a;最少需要 5 个刻度&#xff1b; 方案有&#xff1a; 0, 1, 2, 5, 8 0, 1, 3, 7, 8 0, 1, 4, 6, 8 0, 1, 5, 6, 8 0, 1, 5…

post请求参数全大写后台接不到参数

post请求参数全大写后台接不到参数 开发过程中&#xff0c;我们一般都习惯用驼峰命名法&#xff0c;但是特殊情况要求请求参数全大写&#xff08;或者首字母大写&#xff09;&#xff0c;测试验证的时候发现&#xff0c;接收不到请求参数。 前端请求传递&#xff1a; 服务端接…

【nlp】3.6 Tansformer模型构建(编码器与解码器模块耦合)

Tansformer模型构建(编码器与解码器模块耦合) 1. 模型构建介绍2 编码器-解码器结构的代码实现3 Tansformer模型构建过程的代码实现4 小结1. 模型构建介绍 通过上面的小节, 我们已经完成了所有组成部分的实现, 接下来就来实现完整的编码器-解码器结构耦合. Transformer总体架…

burpsuite的大名早有耳闻,近日得见尊荣,倍感荣幸

问题&#xff1a; burpsuite中文乱码何解&#xff1f; burpsuite 与君初相识&#xff0c;犹如故人归。 burpsuite早有耳闻&#xff0c;近日得见真容&#xff0c;果然非同凡响。 Burp Suite is a comprehensive suite of tools for web application security testing. burp …

spark的算子

spark的算子 1.spark的单Value算子 Spark中的单Value算子是指对一个RDD中的每个元素进行操作&#xff0c;并返回一个新的RDD。下面详细介绍一些常用的单Value算子及其功能&#xff1a; map&#xff1a;逐条映射&#xff0c;将RDD中的每个元素通过指定的函数转换成另一个值&am…

JavaScript基础—运算符、表达式和语句、分支语句、循环语句、综合案例-ATM存取款机

版本说明 当前版本号[20231125]。 版本修改说明20231125初版 目录 文章目录 版本说明目录JavaScript 基础 - 第2天运算符算术运算符赋值运算符自增/自减运算符比较运算符逻辑运算符运算符优先级 语句表达式和语句分支语句if 分支语句if双分支语句if 多分支语句三元运算符&am…

Kotlin学习——kt里的集合List,Set,Map List集合的各种方法之Int篇

Kotlin 是一门现代但已成熟的编程语言&#xff0c;旨在让开发人员更幸福快乐。 它简洁、安全、可与 Java 及其他语言互操作&#xff0c;并提供了多种方式在多个平台间复用代码&#xff0c;以实现高效编程。 https://play.kotlinlang.org/byExample/01_introduction/02_Functio…

vue3(二)-基础入门

一、列表渲染 of 和 in 都是一样的效果 html代码&#xff1a; <div id"app"><ul><li v-for"item of datalist">{{ item }}</li></ul><ul><li v-for"item in dataobj">{{ item }}</li></u…

npm pnpm yarn(包管理器)的安装及镜像切换

安装Node.js 要安装npm&#xff0c;你需要先安装Node.js。 从Node.js官方网站&#xff08;https://nodejs.org&#xff09;下载并安装Node.js。 根据你的需要选择相应的版本。 一路Next&#xff0c;直到Finish 打开CMD&#xff0c;输入命令来检查Node.js和npm是否成功安装 nod…