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

2023-11-26 09:39:15 浏览数 (2)

最近在开发过程中,定位一个问题的时候,发现多线程场景下大量创建和销毁某个C:WindowsSystem32reg.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;
}

运行上面程序,程序会崩溃

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

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

从上面可以看出,线程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;
}

程序运行结果如下:

上面程序的意图很明显:就是先往myMap中放置一些键值对的数据: [0,1],[1,2],[2,3],[3,4],[4,5],[5,6][6,7],[7,8],[8,9],然后在遍历myMap时删除key值大于5的元素。再接着打印操作后的myMap。

从上面的错误可以看出:程序报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;
}

运行结果如下图所示:

参考文章

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

0 人点赞