C++ std::map执行erase操作时崩溃,如何修改代码实现防护?
问题根因
你的崩溃本质是跨锁持有STL容器迭代器导致的迭代器失效问题:
你在获取subs_.begin()后立即释放了互斥锁,此时如果其他线程对subs_执行插入、删除操作,你手里持有的迭代器it会直接变成失效的野迭代器。后续进入循环再加锁访问it时,本质是在访问非法内存,才会出现28473456这类异常ID,最终调用erase时触发崩溃。
修复方案
方案1:遍历全程持锁(简单稳妥,优先选择)
如果遍历逻辑耗时不长,直接把迭代器初始化和整个遍历过程放在同一个锁作用域内,全程持有锁避免其他线程修改容器,从根源上避免迭代器失效:
void SubscManag::handleNotif(Notif notif) { std::cout << "Current subscribe size: " << subs_.size(); // 整个遍历过程持锁,期间其他线程无法修改subs_ std::lock_guard<std::recursive_mutex> lock(mutex_); auto it = subs_.begin(); while (it != subs_.end()) { if (it->second.hasToBeErased) { std::cout << "Erase id : " << it->first; it = subs_.erase(it); continue; } if (cond) { // .... ++it; continue; } // .... ++it; } }
方案2:细粒度锁+批量删除(适合遍历逻辑耗时较长的场景)
如果不想长时间持有锁影响并发性能,可以先收集需要处理/删除的标识,再统一操作,删除前额外校验元素有效性,避免野指针崩溃:
void SubscManag::handleNotif(Notif notif) { std::cout << "Current subscribe size: " << subs_.size(); std::vector<int> toEraseIds; // 第一步:短时间持锁,仅收集需要删除的ID,耗时业务逻辑可拷贝数据后放到锁外执行 { std::lock_guard<std::recursive_mutex> lock(mutex_); for (auto& pair : subs_) { if (pair.second.hasToBeErased) { toEraseIds.push_back(pair.first); } else if (cond) { // 此处把需要处理的数据拷贝到临时变量,不要在锁内执行耗时操作 // tempData.push_back(pair.second); } } } // 第二步:持锁批量删除,删除前先校验元素是否存在 std::lock_guard<std::recursive_mutex> lock(mutex_); for (int id : toEraseIds) { auto it = subs_.find(id); // 确认元素存在再删除,避免重复删除或者元素已被其他线程删除的情况 if (it != subs_.end()) { std::cout << "Erase id : " << id; subs_.erase(it); } } // 锁外执行耗时的业务逻辑处理 // processTempData(tempData); }
额外注意事项
- 不要跨锁持有任何STL容器的迭代器,只要锁释放,其他线程就可能修改容器导致迭代器失效
- 递归互斥锁如果没有必须使用的场景,建议换成普通
std::mutex,性能更好也能降低死锁风险
内容的提问来源于stack exchange,提问作者user16263698
相关产品推荐
相关产品推荐

