最近在开发过程中,定位一个问题的时候,发现多线程场景下大量创建和销毁某个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;
}
运行上面程序,程序会崩溃
线程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迭代器失效写法,代码如下:
#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;
}
程序运行结果如下:
上面程序的意图很明显:就是先往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迭代器删除操作示例
正确的写法如下:
#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容器迭代器失效情况分析、总结
- 迭代器失效的几种情况总结