最近在开发过程中,定位一个问题的时候,发现多线程场景下大量创建和销毁某个C:\Windows\System32\reg.exe
时出现了383个进程创建消息处理的接口,和384个进程销毁处理消息的接口都在等待锁,另外一个线程也在等锁,后面看了一下在处理进程创建和进程销毁的IPC消息处理所在类中有三把锁,执行流程都锁住了,猜测应该是某个线程持有锁没释放,导致其他并发线程锁住了,结合转储的dump和log日志,以及使用VS2017加载对应的dump,对并行堆栈中的线程进行分析,找了很久没发现问题。最后想了一下,是不是某个地方线程做了耗时或者同步阻塞操作导致的,或者线程中执行了死循环,排查后发现是因为一个同事在对map做循环遍历时,erase操作不当,导致某个地方迭代器失效,线程崩溃了,持有两把锁,其他所有线程都拿不到锁,导致IPC消息一直无法发送,最后程序无法升级。
为了上述模拟多线程访问死锁的问题,我简单写了个demo
示例,在main函数中创建了两个线程,其中一个线程对std::map
数据做删除操作,另外一个线程对std::map
做数据打印操作。
代码如下:
#include
#include
#include
#include
#include
#include
// 共享数据
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_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迭代器失效写法,代码如下:
#include
#include
#include
#include
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
异常。
正确的写法如下:
#include
#include
#include
#include
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;
}