前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++ STL map迭代器失效问题

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

作者头像
ccf19881030
发布2023-11-26 09:39:15
3350
发布2023-11-26 09:39:15
举报
文章被收录于专栏:ccf19881030的博客ccf19881030的博客

最近在开发过程中,定位一个问题的时候,发现多线程场景下大量创建和销毁某个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做数据打印操作。 代码如下:

代码语言:javascript
复制
#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);	// 此处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_cityMap
	for (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场景下迭代器失效导致线程崩溃
多线程访问map场景下迭代器失效导致线程崩溃
迭代器失效
迭代器失效
并行堆栈示意图
并行堆栈示意图

线程1在thread_func1函数的第26行执行g_cityMap.erase(iter);操作后,iter迭代器就失效了,导致跳转到for (auto iter = g_cityMap.begin(); iter != g_cityMap.end(); iter++) {这条语句中的iter++操作时,线程1所在线程会崩溃,如下图所示:

线程1的执行堆栈
线程1的执行堆栈

再来看一下线程2(对应线程ID为7236)的执行堆栈,如下图所示:

线程2的执行堆栈
线程2的执行堆栈

从上面可以看出,线程7236在代码第37行执行加锁处卡住了,因为g_cityMapMutex被线程19004持有未释放,此时线程7236会被卡住。

map迭代器失效问题

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

代码语言:javascript
复制
#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));
  }

  // 打印myMap
  std::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);
  	}
  }

  // 打印剩余的myMap
  std::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迭代器失效导致程序崩溃
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迭代器失效
map迭代器失效

从上面的错误可以看出:程序报cannot increment value-initialized map/set iterator异常。

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

正确的写法如下:

代码语言:javascript
复制
#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));
	}

	// 打印myMap
	std::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++;
		}
	}

	// 打印剩余的myMap
	std::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;
}

运行结果如下图所示:

正确写法运行结果
正确写法运行结果

参考文章

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-11-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • map迭代器失效问题
  • 正确的map迭代器删除操作示例
  • 参考文章
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档