You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多线程下如何确保std::unordered_map中对象存在以避免崩溃?

解决std::unordered_map异步遍历与删除的段错误问题

问题场景

维护一个存储DataClass实例的std::unordered_map,存在两个异步运行的定时器:

  • 周期1毫秒的定时器,遍历map对DataClass内的数据执行数学运算
  • 一次性超时定时器,超时值由DataClass的成员变量定义,触发时发送网络消息并删除对应map键值对

问题:遍历过程中若触发超时删除操作,会导致核心转储(段错误11),需在遍历中确保键值对有效性以避免错误。

原始代码

struct DataClass
{
    int id;
    //Some Other Data
    unsigned int timeout;
};

std::unordered_map<int, DataClass> data_map = 
{
    {1, {1, 100}},
    {2, {2, 200}},
    {3, {3, 300}}
};

void run_every_1ms(void)
{
    for (auto &[key, value] : data_map) //How do I check if particular key is detected while executing for loop
    {
        std::cout << "ID : " << key << "\t";
        std::cout << "Timeout : " << value.timeout << std::endl;
    }
}

void timeout(int id)
{
    //Send message external system
    data_map.erase(id); //Problem if erase is executed during the for loop execution in run_every_1ms
}

解决方案

方法1:互斥锁同步访问

通过互斥锁保证同一时间只有一个线程操作map,彻底避免并发修改导致的迭代器失效。

#include <mutex>

std::mutex map_mutex;
std::unordered_map<int, DataClass> data_map = 
{
    {1, {1, 100}},
    {2, {2, 200}},
    {3, {3, 300}}
};

void run_every_1ms(void)
{
    std::lock_guard<std::mutex> lock(map_mutex); // 自动加锁,作用域结束自动释放
    for (auto &[key, value] : data_map)
    {
        std::cout << "ID : " << key << "\t";
        std::cout << "Timeout : " << value.timeout << std::endl;
        // 执行你的数学运算逻辑
    }
}

void timeout(int id)
{
    // 若网络消息发送耗时,建议将此部分移到锁外,减少锁持有时间
    // Send message to external system

    std::lock_guard<std::mutex> lock(map_mutex);
    data_map.erase(id);
}

说明:这是最稳妥的方案,适合大多数场景。注意将耗时操作(如网络IO)移出锁范围,避免阻塞遍历线程。

方法2:复制键集合+遍历中检查存在性

先复制当前map的所有键到临时容器,遍历临时容器时,每次操作前检查原map中键是否仍存在。此方法能减少锁的竞争时间,适合遍历频繁、删除较少的场景。

#include <mutex>
#include <vector>

std::mutex map_mutex;
std::unordered_map<int, DataClass> data_map = 
{
    {1, {1, 100}},
    {2, {2, 200}},
    {3, {3, 300}}
};

void run_every_1ms(void)
{
    // 短时间加锁复制所有键
    std::vector<int> keys;
    {
        std::lock_guard<std::mutex> lock(map_mutex);
        for (const auto& pair : data_map)
        {
            keys.push_back(pair.first);
        }
    }

    // 遍历复制的键,每次操作前检查键是否存在
    for (int key : keys)
    {
        std::lock_guard<std::mutex> lock(map_mutex);
        auto it = data_map.find(key);
        if (it == data_map.end())
        {
            continue; // 键已被删除,跳过当前项
        }
        // 访问it->second执行数学运算
        std::cout << "ID : " << key << "\t";
        std::cout << "Timeout : " << it->second.timeout << std::endl;
    }
}

void timeout(int id)
{
    // 网络消息发送建议移到锁外
    // Send message to external system

    std::lock_guard<std::mutex> lock(map_mutex);
    data_map.erase(id);
}

说明:复制键的操作仅需短暂加锁,遍历过程中每次加锁时间也很短,能有效降低线程阻塞概率。

方法3:使用并发安全容器(C++17需依赖第三方库)

C++17本身没有内置的并发安全无序map,可使用第三方库如Boost的boost::concurrent_unordered_map,这类容器原生支持并发访问与修改,无需手动加锁。但需注意引入第三方库的依赖成本。

核心原则

  • std::unordered_map不是线程安全容器,并发修改和遍历必然导致迭代器失效、内存访问错误,必须通过同步机制避免。
  • 锁的粒度越小越好,尽量减少锁的持有时间,避免影响整体性能。

内容的提问来源于stack exchange,提问作者Dark Sorrow

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 16:23:13