最简单的并发 C++11 字符串键控映射
Posted
技术标签:
【中文标题】最简单的并发 C++11 字符串键控映射【英文标题】:Simplest concurrent C++11 string keyed map 【发布时间】:2018-11-26 10:45:29 【问题描述】:像许多 C++ 开发人员一样,我需要一个简单的并发字符串键控表,并且我希望它仅基于 C++11 标准库。
“并发”是指多个线程可以在不相互锁定的情况下处理它(大部分时间)。
更新:有两种流行的解决方案可用。并不完全“简单”,但功能丰富且性能卓越:
junction(BSD 许可证) Intel's TBB(Apache 许可证)此外,为了节省下一个工作人员/gal 的时间,我正在分享我能够组合的最简单 C++11 解决方案(~40 LOC)。
到目前为止的反馈非常好,它帮助我找到了现有的选项并改进了我的简单答案。很高兴看到其他简单的答案出现。
【问题讨论】:
并发和简单通常是互斥的。 @gatopeich 什么是“字符串映射”?您的问题是关于字符串键控映射。散列放大器通常支持任意类型的键,只要它们有散列(并且字符串满足这一点)。 很抱歉,您的问题是什么?共享一个特定的(并且可以说不是最好的)实现不是问题。 还有什么问题,例如待定concurrent_unordered_map
/concurrent_hash_map
?
@gatopeich 因为您提出的问题无论如何都不是主题。我建议改为将您的“解决方案”发布到 codereview.stackexchange.com 以获得更好的反馈。
【参考方案1】:
经过一番研究,您最终会得到以下两种选择之一:
围绕一些超快速哈希表实现 around。导入相当复杂/混淆的第 3 方代码,将您的密钥转换为哈希,处理冲突,玩得开心。
将您的表划分为较小的锁定表。使用共享指针来管理包含的数据。
选择 #2 解决了我当前的用例,几乎没有争用,同时也没有关闭未来优化的大门你会需要它。它实际上解决了您“成长”为更复杂的解决方案所需的一些事情。方法如下:
template <typename Element, unsigned NumBlocks>
class ConcurrentTable
using SharedElement = std::shared_ptr<Element>;
class InnerMap
std::mutex mut_;
std::unordered_map<std::string,SharedElement> map_;
public:
SharedElement get(std::string const& key)
std::unique_lock<mutex> lock(mut_);
auto i = map_.find(key);
return (i != map_.end()) ? i->second
: SharedElement; // Empty pointer == not found
bool set(std::string const& key, SharedElement && value)
std::unique_lock<mutex> lock(mut_);
auto [_,created] = map_.insert_or_assign(key, forward<SharedElement>(value));
return created;
bool del(std::string const& key)
unique_lock<mutex> lock(mut_);
return map_.erase(key);
;
std::array<InnerMap, NumBlocks> maps_;
std::hash<std::string> hash_;
InnerMap& map_for(std::string const& key)
return maps_[hash_(key) % NumBlocks];
public:
SharedElement get(std::string const& key)
return map_for(key).get(key);
bool set(std::string const& key, SharedElement && value)
return map_for(key).set(key, std::forward<SharedElement>(value));
bool del(std::string const& key)
return map_for(key).del(key);
;
现在您可以将一个并发的字符串表实例化为这样的字符串:
ConcurrentTable<std::string,128> my_concurrent_table;
有 128 个子表有足够的哈希空间,例如10 个并发线程随机访问表。如果您需要更多线程或更少争用,请增加大小。
归功于 SO 贡献者,他的话激发了我的解决方案:“如果您正在执行多线程访问,那么无论如何您都必须进行划分”。 (对不起,写在我的脑后,找不到原文!)
【讨论】:
您的get
可以创建元素(该用户无法更改BTW)。
我相信这个实现不是线程安全的,因为map_for
函数有一个竞争条件。与简单的“std::*map + mutex”和 1 个线程与 2/4 个线程相比,看到一些性能基准测试也很不错。 (以确保它不会表现更差)。
@Jarod42, "my" get 可以创建一个默认构造的shared_ptr
,因此您可以在那里检查无效性。这对我有用而且很简单,但我会让它抛出只是为了让你开心:-)
@DanM.,我非常确定 map_for()
没有竞争条件。它只是对常量字符串值进行哈希处理。除非std::hash
本质上被破坏了,否则就是这样。
@DanM.:首先maps_
是array
,每个组件都通过mutex
保护,所以看起来不错。 (Element
不受保护,但似乎超出范围)以上是关于最简单的并发 C++11 字符串键控映射的主要内容,如果未能解决你的问题,请参考以下文章