接口深度解析)
mold 内置 TBB 并发哈希容器concurrent_unordered_multimap 查找Lookup接口深度解析【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/moldmold 链接器在构建产物中内置了 Intel oneAPI TBB 并发容器作为第三方依赖其中oneapi::tbb::concurrent_unordered_multimap是一个支持并发插入、查找与遍历但不支持并发删除的无序关联容器且允许多个元素拥有等价键。本文以 TBB 规范文档中的 Lookup 章节为主体逐一解析该容器四个查找接口count、find、contains、equal_range的语义、重载规则与并发安全性并结合 容器基类实现 与 一致性测试 剖析其底层原理。读完本文你将能在多线程场景下正确、高效地使用这些查找接口并理解透明哈希heterogeneous lookup重载的启用条件。查找接口的并发安全承诺依据规范文档 lookup.rst 的开篇声明本节描述的所有方法都可以相互并发执行也可以与所有并发安全的修改操作modifiers以及容器的遍历操作并发执行。这一承诺是该容器的核心价值在多线程环境下读者线程无需加锁即可执行查找写者线程可同时进行插入二者互不阻塞。需要强调的是规范同时指出该容器不支持并发删除“supports concurrent insertion, lookup, and traversal, but does not support concurrent erasure”见 concurrent_unordered_multimap.rst 的概述段落实际路径为 concurrent_unordered_multimap.rst。删除操作属于unsafe_modifiers不安全修改器范畴unsafe_modifiers.rst 中给出了详细说明——在调用不安全修改器期间不允许其他线程并发访问容器。因此Lookup 接口的并发安全保证适用于“读取 并发安全写入”的组合使用时必须把erase一类不安全操作排除在并发访问窗口之外。类模板与类型别名回顾concurrent_unordered_multimap定义在头文件oneapi/tbb/concurrent_unordered_map.h中concurrent_unordered_map.h模板形参为template typename Key, typename T, typename Hash std::hashKey, typename KeyEqual std::equal_toKey, typename Allocator tbb_allocatorstd::pairconst Key, T class concurrent_unordered_multimap;其中key_type为Keymapped_type为Tvalue_type为std::pairconst Key, T。从源码结构看concurrent_unordered_multimap通过concurrent_unordered_map_traitsKey, T, Hash, KeyEqual, Allocator, true继承自基类concurrent_unordered_baseconcurrent_unordered_map.h其第 6 个模板参数true表示“允许多重映射allow_multimapping”——这正是 multimap 允许键重复的开关。所有 Lookup 接口本身均由基类实现派生类仅通过using base_type::...继承暴露因此本节语义对concurrent_unordered_map单键同样成立只是 multimap 特有的“多个等价键”行为需要按下面的说明处理。接口一count —— 统计等价键元素个数size_type count( const key_type key ); template typename K size_type count( const K key );返回语义容器中键与key等价equivalent的元素个数。对 multimap 而言由于允许多个元素共享同一键返回值可能大于 1。基类实现位于 _concurrent_unordered_base.hsize_type count( const key_type key ) const { return internal_count(key); } template typename K typename std::enable_ifis_transparentK::value, size_type::type count( const K key ) const { return internal_count(key); }注意两点实现细节模板重载通过std::enable_ifis_transparentK::value, ...约束这与规范中“仅当hasher::transparent_key_equal合法且表示一个类型时才参与重载决议”的描述完全一致is_transparent即对透明性特征进行检测的别名。internal_count_concurrent_unordered_base.h在 multimap 模式下直接复用equal_range并计算区间距离template typename K size_type internal_count( const K key ) const { if (allow_multimapping) { // TODO: consider reimplementing the internal_equal_range with elements counting to avoid std::distance auto eq_range equal_range(key); return std::distance(eq_range.first, eq_range.second); } else { return contains(key) ? 1 : 0; } }源码中的 TODO 注释也提示multimap 的 count 走的是“查区间再数距离”的路径成本与等价键数量成正比而单键 map 走的是contains快速路径复杂度为 O(1) 期望。从源码结构可以推断如果读者只需要判断“是否存在”而不关心数量contains会比count更廉价。接口二find —— 定位等价键元素iterator find( const key_type key ); const_iterator find( const key_type key ) const; template typename K iterator find( const K key ); template typename K const_iterator find( const K key ) const;返回语义返回指向键与key等价元素的迭代器若不存在则返回end()。关键约定当存在多个等价键元素时找到哪一个元素是未指定的unspecified——实现没有义务返回“第一个”或“最后一个”调用方不应依赖返回值相对于其他等价键的先后位置。基类实现_concurrent_unordered_base.h通过非 const 版本统一调用internal_findconst 版本则const_cast后复用同一逻辑避免代码重复。底层internal_find_concurrent_unordered_base.h体现了该容器“split-ordered list分段有序链表”的核心数据结构template typename K value_node_ptr internal_find( const K key ) { sokey_type hash_key sokey_type(my_hash_compare(key)); sokey_type order_key split_order_key_regular(hash_key); node_ptr curr prepare_bucket(hash_key); while (curr ! nullptr) { if (curr-order_key() order_key) { // 若节点有序键已大于目标则目标必然不在表中 return nullptr; } else if (curr-order_key() order_key my_hash_compare(traits_type::get_key(static_castvalue_node_ptr(curr)-value()), key)) { // 有序键相同并不代表元素相等仍需调用 key 比较函数确认 return static_castvalue_node_ptr(curr); } curr curr-next(); } return nullptr; }查找过程分三层先对键做哈希并换算成“分段有序键”split-order key随后prepare_bucket确保目标桶对应的链表段已初始化最后沿链表线性推进——有序键大于目标时提前终止借助有序性剪枝有序键相等时再用key_equal做最终比对。代码注释特别强调“有序键相同并不意味着找到了元素”必须经过键等价性比较才能确认命中这保证了即使不同键哈希碰撞到同一有序键也不会产生误报。接口三contains —— 是否存在等价键bool contains( const key_type key ) const; template typename K bool contains( const K key ) const;返回语义容器中至少存在一个键与key等价的元素时返回true否则返回false。它不关心等价键的数量因此语义上等价于find(key) ! end()。基类实现_concurrent_unordered_base.h确实就是这么做的bool contains( const key_type key ) const { return find(key) ! end(); } template typename K typename std::enable_ifis_transparentK::value, bool::type contains( const K key ) const { return find(key) ! end(); }在并发场景下contains通常比count更受青睐它只需找到第一个匹配即可提前返回且语义清晰。一致性测试中也能看到该接口的直接使用例如 concurrent_unordered_common.h 在遍历校验时以if (!c.contains(ValueUnorderedType::key(*it)))断言每个元素键都仍在容器中。接口四equal_range —— 获取等价键区间std::pairiterator, iterator equal_range( const key_type key ); std::pairconst_iterator, const_iterator equal_range( const key_type key ) const; template typename K std::pairiterator, iterator equal_range( const K key ); template typename K std::pairconst_iterator, const_iterator equal_range( const K key ) const;返回语义若存在至少一个键与key等价的元素返回迭代器对{f, l}其中f指向第一个等价键元素l指向最后一个等价键元素之后的那个元素即经典左闭右开区间[f, l)若不存在任何等价键元素返回{end(), end()}。对 multimap 而言这是枚举某个键下全部关联值的标准手段。基类实现委托给internal_equal_range_concurrent_unordered_base.h其逻辑与internal_find同构先定位到第一个匹配节点然后沿链表继续推进只要后续节点不是哨兵节点且键仍与目标等价就继续后移循环条件中的allow_multimapping last ! nullptr !last-is_dummy() key_equal(..., key)保证了 multimap 模式下能跨越全部等价键最终返回{first, first_value_node(last)}。正因为要跨越所有等价键equal_range的复杂度与等价键数量线性相关。典型用法遍历某键下的全部映射值oneapi::tbb::concurrent_unordered_multimapstd::string, int table; // ... 并发插入若干 (key, v) 对 ... auto [first, last] table.equal_range(key); for (auto it first; it ! last; it) { // it-first 恒等于 keyit-second 为各关联值 process(it-second); }由于 multimap 不保证等价键之间的顺序[f, l)区间内元素的先后排列同样是未指定的业务逻辑不应依赖该顺序。透明键查找Heterogeneous Lookup模板重载的启用条件四个接口都提供了以template typename K声明的透明重载。这类重载允许用与key_type不同类型的键执行查找——例如用std::string_view或const char*去查std::string键的容器从而避免临时构造std::string的开销。但规范明确规定该重载仅当限定名hasher::transparent_key_equal合法且表示一个类型时才参与重载决议。在 TBB 实现中这一机制的判定链条如下_containers_helpers.h 中的特征类has_transparent_key_equal专门检测Hash::transparent_key_equal是否存在template typename Key, typename Hasher, typename KeyEqual, typename void struct has_transparent_key_equal : std::false_type { using type KeyEqual; }; template typename Key, typename Hasher, typename KeyEqual, typename struct has_transparent_key_equalKey, Hasher, KeyEqual, tbb::detail::void_ttypename Hasher::transparent_key_equal : std::true_type { // 并静态断言 transparent_key_equal::is_transparent 必须合法 static_assert(comp_is_transparenttype::value, Hash::transparent_key_equal::is_transparent is not valid or does not denote a type.); };_hash_compare.h 中的hash_compare依据该特征把key_equal解析为has_transparent_key_equal::type并对外提供透明版本的重载运算符operator()(K key)与operator()(K1, K2)——这些重载同样用std::enable_ifis_transparent_hash::value, ...约束确保只有哈希器声明了透明性时才参与重载。基类查找接口前述find、count、contains、equal_range的模板版本再用std::enable_ifis_transparentK::value, ...收口。启用方式自定义哈希器时在哈希器内部提供一个名为transparent_key_equal的嵌套类型并让该类型带is_transparent标记。典型写法struct string_hash { using transparent_key_equal std::equal_to; // 声明透明等价性 std::size_t operator()(std::string_view s) const { return std::hashstd::string_view{}(s); } std::size_t operator()(const std::string s) const { return std::hashstd::string{}(s); } }; oneapi::tbb::concurrent_unordered_multimapstd::string, int, string_hash table; // 直接以 const char* / std::string_view 查找避免构造临时 std::string auto n table.count(literal-key); if (table.contains(std::string_view(sv-key))) { /* ... */ }若哈希器未声明transparent_key_equal模板重载会被 SFINAE 掉调用会回落到key_type版本此时传入异构键会经历隐式转换。规范中“只参与重载决议only participates in overload resolution”的措辞与std::enable_if的 SFINAE 约束在语义上完全对应。并发查找的正确姿势与易错点综合规范与实现使用这四个查找接口时有几点值得注意返回值的时效性所有 Lookup 接口都是即时快照。find返回的迭代器指向的元素可能随后被其他线程通过并发安全的修改器更新节点按值存储但不会被删除——因为删除必须走unsafe_modifiers而那种操作要求独占访问不允许与查找并发。因此只要遵守“不安全修改器独占”的约定查找迭代器就不会悬空。不要依赖等价键之间的返回位置find在多等价键时返回“哪一个”是未指定的equal_range区间内顺序也是未指定的。countvscontains只关心“有没有”时优先contains底层即find ! end()需要精确数量时用countmultimap 下走区间距离计算。异构键查找的前提想用std::string_view/const char*这类键查找必须让哈希器声明transparent_key_equal否则模板重载不参与决议。这些语义在仓库的测试套件中有据可查一致性测试 conformance_concurrent_unordered_map.cpp 与公共测试头 concurrent_unordered_common.h 覆盖了contains遍历校验、桶遍历计数等场景而透明查找特征与static_assert的检查路径可在 _containers_helpers.h 中直接阅读便于验证自定义哈希器的声明是否符合要求。小结concurrent_unordered_multimap的 Lookup 章节定义了四个语义清晰、可并发安全的查找接口count统计等价键数量、find定位单个等价键元素、contains判定存在性、equal_range获取完整的等价键区间。它们都提供基于hasher::transparent_key_equal门控的异构键重载底层由 split-ordered list 与分段桶结构支撑查找过程借助有序键剪枝与键等价性二次比对保证正确性。在 mold 所集成的这套 TBB 容器中理解这些接口的语义边界尤其是“多等价键时结果未指定”与“删除需独占”是在多线程环境中写出正确代码的前提。【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考