最简单的并发 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 字符串键控映射的主要内容,如果未能解决你的问题,请参考以下文章

通过在迭代指针键控映射上出错来捕获不确定性

在PHP中映射路由的最简单方法

常见调制技术汇总

哈希映射

在 C# 字符串中摆脱零宽度空间的最简单方法

在 Flink 中用 (X) 键控的缓慢变化流来丰富 (X,Y) 键控的快速流