![](/img/trans.png)
[英]Data race with std::unordered_map, despite locking insertions with mutex
[英]std::unordered_map: multithreading insertions?
我有一堆数据(0和ULLONG_MAX之间的一个巨大的整数列表),我想提取所有唯一值。 我的方法是创建一个unordered_map,使用整数列表值作为键,并为地图值使用一次性bool。 我迭代列表并为每个键插入一次性值。 最后,我迭代地图以获得所有唯一键。 挺直的。
但是,我的列表是如此之大(100亿),我想多线程这个过程。 我知道一种天真的线程方法是行不通的,因为unordered_map插入会影响底层数据结构,所以它不是线程安全的。 并且在每次插入时添加锁定将是缓慢的并且可能否定任何线程加速。
但是,大概不是每次插入都会改变数据结构(只有那些不适合现有分配的存储桶?)。 有没有办法在插入之前检查特定插入是否需要unordered_map重新分配? 这样我只能在地图变更时锁定线程,而不是在每次插入时锁定。 然后,在每次插入之前,线程仅检查锁是否存在...而不是完全锁定/解锁。 那可能吗?
并行化的基本规则打破了工作,处理碎片,然后组合碎片。
散列/项目查找是整个shebang中最昂贵的部分,因此我们将专注于并行化。
如果您绝对需要将结果作为哈希表,我会收到一些坏消息:您必须自己编写。 话虽这么说,让我们开始吧。
首先,让我们连续解决问题。 这很简单。 以下函数采用向量和回调。 我们将获取向量,将其转换为unordered_set
,并将unordered_set
给回调。 简单? 是。
现在,因为我们将在一个线程上执行此操作,所以我们无法立即执行此操作。 相反,我们将返回一个不带参数的lambda。 当调用lambda时,就会创建unordered_set
并将其提供给回调。 这样,我们可以将每个lambda赋予它自己的线程,并且每个线程将通过调用lambda来运行该作业。
template<class Vector, class Callback>
auto lazyGetUnique(Vector& vector, Callback callback) {
using Iterator = decltype(vector.begin());
auto begin = vector.begin();
auto end = vector.end();
using elem_t = typename std::iterator_traits<Iterator>::value_type;
//We capture begin, end, and callback
return [begin, end, callback]() {
callback(std::unordered_set<elem_t>(begin, end));
};
}
现在 - 这个回调应该做什么? 答案很简单:回调应该将unordered_set
的内容分配给一个向量。 为什么? 因为我们要合并结果,合并向量比合并unordered_set
要快得多。
让我们编写一个函数来给我们回调:
template<class Vector>
auto assignTo(Vector& v) {
return [&](auto&& contents) {
v.assign(contents.begin(), contents.end());
};
}
假设我们想要获取向量的唯一元素,并将它们分配回该向量。 现在这很简单:
std::vector<int> v = /* stuff */;
auto new_thread = std::thread( lazyGetUnique(v, assignTo(v)) );
在此示例中,当new_thread
完成执行时, v
将仅包含唯一元素。
让我们来看看完成所有功能的完整功能。
template<class Iterator>
auto getUnique(Iterator begin, Iterator end) {
using elem_t = typename std::iterator_traits<Iterator>::value_type;
std::vector<elem_t> blocks[4];
//Split things up into blocks based on the last 4 bits
//Of the number. This allows us to guarantee that no two blocks
//share numbers.
for(; begin != end; ++begin) {
auto val = *begin;
blocks[val & 0x3].push_back(val);
}
//Each thread will run their portion of the problem.
//Once it's found all unique elements, it'll stick the result in the block used as input
auto thread_0 = std::thread( lazyGetUnique(blocks[0], assignTo(blocks[0])) );
auto thread_1 = std::thread( lazyGetUnique(blocks[1], assignTo(blocks[1])) );
auto thread_2 = std::thread( lazyGetUnique(blocks[2], assignTo(blocks[2])) );
//We are thread_3, so we can just invoke it directly
lazyGetUnique(blocks[3], assignTo(blocks[3]))(); //Here, we invoke it immediately
//Join the other threads
thread_0.join();
thread_1.join();
thread_2.join();
std::vector<elem_t> result;
result.reserve(blocks[0].size() + blocks[1].size() + blocks[2].size() + blocks[3].size());
for(int i = 0; i < 4; ++i) {
result.insert(result.end(), blocks[i].begin(), blocks[i].end());
}
return result;
}
这个函数将东西分成4个块,每个块都是不相交的。 它在4个块中的每个块中找到唯一元素,然后组合结果。 输出是一个向量。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.