在这种情况下std::unordered_map线程安全吗?



假设一个线程只连续调用以下函数。
这里,insert_data检查std::unordered_map中是否存在一个键,如果不存在,insert_data调用一个函数添加一个新的键并修改它的值。

void insert_data(int key, int value, std::unordered_map<int, std::vector<int>>& my_map)
{
if (my_map.find(key) == my_map.end())
{
my_map[key] = std::vector<int>();
}
my_map[key].push_back(value);
}

在另一个线程中,它遍历std::unordered_map。

void iteration(std::unordered_map<int, std::vector<int>>& my_map)
{
for (auto& [key, value] : my_map)
{
std::cout<<"key : "<<key<<" value : "<<value<<std::endl;
}
}

如果上述每个函数仅在一个线程中执行,那么共享my_map线程安全吗?

不,那不安全。更改STL容器大小的操作永远都不是线程安全的。

也可以使插入更有效:

void insert_data(int key, int value,
std::unordered_map<int, std::vector<int>>& my_map)
{
my_map[key].push_back(value);
}

如果该值不存在,[key]操作符将自动创建该值。即使您的代码想知道它是否是一个新条目,您也可以这样做:

void insert_data(int key, int value,
std::unordered_map<int, std::vector<int>>& my_map)
{
auto inserted = my_map.emplace(key, std::vector<int>{});
inserted.first->second.push_back(value);
bool new_entry = inserted.second;
}

这避免了重复查找。它构造了一个零大小的临时向量,但这很便宜。

简单修复

最简单的解决方案是用互斥锁来保护整个过程。

class Dict
{
std::mutex mutex;
std::unordered_map<int, std::vector<int>> map;
public:
void insert_data(int key, int value)
{
std::lock_guard<std::mutex> lock(mutex);
map[key].push_back(value);
}
void iteration()
{
std::lock_guard<std::mutex> lock(mutex);
for(const auto& key_values: map)
for(int value: key_values.second)
std::cout << "key : " << key_values.first
<< " value : " << value << 'n';
}
};

主要的问题是现在插入可以等待很长一段时间,直到它可以进行。

<<h2>缓冲修复/h2>为了避免这些长延迟,我们应该尽可能地将两个线程解耦。像这样:

class Dict
{
std::unordered_map<int, std::vector<int>> map;
std::mutex deferred_mutex;
std::unordered_map<int, std::vector<int>> deferred;
public:
void insert_data(int key, int value)
{
std::lock_guard<std::mutex> lock(deferred_mutex);
deferred[key].push_back(value);
}
void iteration()
{
std::unordered_map<int, std::vector<int>> new_elements;
std::unique_lock<std::mutex> deferred_lock(deferred_mutex);
deferred.swap(new_elements);
deferred_lock.unlock();
for(auto& [key, new_values]: new_elements) {
std::vector<int>& values = map[key];
values.insert(values.end(), new_values.begin(),
new_values.end());
}
for(const auto& key_values: map)
for(int value: key_values.second)
std::cout << "key : " << key_values.first
<< " value : " << value << 'n';
}
};
基本上,我们将新元素分开保存,直到稍后可以插入它们。与执行IO的成本相比,iteration()线程的额外工作应该可以忽略不计。

键值对的vector<pair<int, int>>可能比第二个unordered_map更有效,但这需要基准测试和键重复频率的知识。

相关内容

  • 没有找到相关文章

最新更新