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.hqwc.cn/news/218656.html

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

相关文章

SQL sever2008中的游标

目录 一、游标概述 二、游标的实现 三、优缺点 3.1优点&#xff1a; 3.2缺点&#xff1a; 四、游标类型 4.1静态游标 4.2动态游标 4.3只进游标 4.4键集驱动游标 4.5显示游标&#xff1a; 4.6隐式游标 五、游标基本操作 5.1声明游标 5.1.1.IS0标准语法 5.1.1.1语…

【LLM_04】自然语言处理基础_2

一、神经网络1、循环神经网络&#xff08;RNN&#xff09;2、门控循环单元&#xff08;GRU&#xff09;3、长短期记忆网络&#xff08;LSTM&#xff09;4、双向RNN5、卷积神经网络&#xff08;CNN&#xff09; 二、注意力机制1、注意力机制原理介绍2、注意力机制的各种变式3、注…

Linux操作系统虚拟机安装(图文详解)

目录 前言 Linux系统介绍 虚拟机安装 1.安装步骤 2.破解激活步骤 3.创建Linux系统虚拟机 虚拟机的相关设置 1.基础设置 2.语言设置为中文 前言 今天我们开始学习Linux操作系统的安装虚拟机以及相关的Linux的环境配置&#xff0c;后面我还会继续发布Linux系统的相关基…

杰发科技AC7801——keil工程移植到IAR

0、简介 发现AC7801的代码只有keil工程的&#xff0c;IAR和Eclipse的代码只有一个例程&#xff0c;于是在从Keil移植到IAR时候遇到的问题记录下。 正常情况下&#xff0c;直接把keil的usr用户代码移植到iar的文件夹下面&#xff0c;删除原本的文件再添加新加进来的文件即可。…

深入浅出 Linux 中的 ARM IOMMU SMMU II

SMMU 驱动中的系统 I/O 设备探测 要使系统 I/O 设备的 DMA 内存访问能通过 IOMMU&#xff0c;需要将系统 I/O 设备和 IOMMU 设备绑定起来&#xff0c;也就是执行 SMMU 驱动中的系统 I/O 设备探测。总线发现系统 I/O 设备并和对应的驱动程序绑定&#xff0c;与 IOMMU 设备驱动程…

cephadm部署ceph quincy版本

环境说明 IP主机名角色 存储设备 192.168.2.100 master100 mon,mgr,osd,mds,rgw 大于5G的空设备192.168.2.101node101mon,mgr,osd,mds,rgw大于5G的空设备192.168.2.102node102mon,mgr,osd,mds,rgw大于5G的空设备 关闭防火墙 关闭并且禁用selinux 配置主机名/etc/hosts …

中伟视界:AI智能分析盒子实现全方位人车监测,保障管道安全

在油气管道长又无人的场景下&#xff0c;人和车的监测问题一直是一个难题。传统的监测手段往往存在盲区和误报问题&#xff0c;给管道运行安全带来了一定的隐患。然而&#xff0c;随着人工智能技术的不断发展&#xff0c;利用AI盒子的智能分析算法可以有效解决这一问题。 首先&…

Everything进行内网穿透搜索

文章目录 1\. 部署内网穿透1.1. 注册账号1.2. 登录1.3. 创建隧道 2\. 从外网访问Everything 借助cpolar可以让我们在公网上访问到本地的电脑 1. 部署内网穿透 1.1. 注册账号 在使用之前需要先进行注册cpolar cpolar secure introspectable tunnels to localhost 1.2. 登录 C…

cesium轨迹线(发光轨迹线)

cesium轨迹线(发光轨迹线) 下面有源码 实现思路 使用ellipse方法加载圆型,修改polyline中‘material’方法重写glsl来实现当前效果(cesium版本1.109) 示例代码 index.html <!DOCTYPE html> <html lang="en"><head

【LeetCode】挑战100天 Day16(热题+面试经典150题)

【LeetCode】挑战100天 Day16&#xff08;热题面试经典150题&#xff09; 一、LeetCode介绍二、LeetCode 热题 HOT 100-182.1 题目2.2 题解 三、面试经典 150 题-183.1 题目3.2 题解 一、LeetCode介绍 LeetCode是一个在线编程网站&#xff0c;提供各种算法和数据结构的题目&…

【LeetCode】挑战100天 Day15(热题+面试经典150题)

【LeetCode】挑战100天 Day15&#xff08;热题面试经典150题&#xff09; 一、LeetCode介绍二、LeetCode 热题 HOT 100-172.1 题目2.2 题解 三、面试经典 150 题-173.1 题目3.2 题解 一、LeetCode介绍 LeetCode是一个在线编程网站&#xff0c;提供各种算法和数据结构的题目&…

【Docker】从零开始:12.容器数据卷

【Docker】从零开始&#xff1a;12.容器数据卷 1.什么是容器数据库卷2.数据的覆盖问题3.为什么要用数据卷4.Docker提供了两种卷&#xff1a;5.两种卷的区别6.bind mount7.Docker managed volumevolume 语法volume 操作参数 1.什么是容器数据库卷 卷 就是目录或文件&#xff0c…