☰
oneTBB concurrent_unordered_map 观察者(Observers)详解:get_allocator / hash_function / key_eq 的语义与源码实现
2026/10/9 2:19:27 网站建设 项目流程
  • 并发编程
  • 高性能计算

【免费下载链接】oneTBB

oneAPI Threading Building Blocks (oneTBB)

项目地址:https://gitcode.com/gh_mirrors/on/oneTBB
点击查看免费下载

oneTBB(oneAPI Threading Building Blocks)提供的oneapi::tbb::concurrent_unordered_map是支持并发插入、查找与遍历的无序关联容器,其"观察者(Observers)"成员函数用于查询容器的底层配置:get_allocator()返回内存分配器、hash_function()返回哈希函数、key_eq()返回键相等性判定谓词。本文以官方参考文档 Observers 为骨架,结合仓库中 concurrent_unordered_map.h 及底层实现 _concurrent_unordered_base.h 的源码,系统讲解这三个观察者的签名、返回值语义、透明哈希(transparent hash)机制与典型使用场景,帮助你写出语义准确、可维护的并发容器代码。

一、观察者方法在类模板中的地位

oneapi::tbb::concurrent_unordered_map的完整类模板定义位于头文件oneapi/tbb/concurrent_unordered_map.h,其声明骨架如下(详见 concurrent_unordered_map_cls.rst):

// Defined in header <oneapi/tbb/concurrent_unordered_map.h> namespace oneapi { namespace tbb { template <typename Key, typename T, typename Hash = std::hash<Key>, typename KeyEqual = std::equal_to<Key>, typename Allocator = tbb_allocator<std::pair<const Key, T>>> class concurrent_unordered_map { public: using hasher = Hash; using key_equal = /*See below*/; using allocator_type = Allocator; // ... // Observers allocator_type get_allocator() const; hasher hash_function() const; key_equal key_eq() const; // ... }; } }

观察者方法与构造、析构、复制、迭代器、容量、并发安全/非安全修改、元素访问、查找、桶接口、哈希策略(load factor、rehash、reserve)以及并行区间迭代共同构成该容器的完整成员函数集合。它们的作用是读取容器在构造时被配置的"三大件"——分配器、哈希函数、键相等谓词,供调用方在容器外部复现相同的配置逻辑(例如复制构造另一个同配置容器、做内存统计、调试键冲突)。

从源码结构看,concurrent_unordered_map本体(include/oneapi/tbb/concurrent_unordered_map.h)继承自concurrent_unordered_base<concurrent_unordered_map_traits<Key, T, Hash, KeyEqual, Allocator, false>>,其观察者接口实际由基类concurrent_unordered_base实现,而concurrent_unordered_multimap则使用allow_multimapping = true的同一套 traits,因此两个容器共享相同的观察者语义。

二、get_allocator():获取分配器副本

2.1 接口语义

observers.rst给出的签名与返回语义为:

allocator_type get_allocator() const;

Returns:返回与*this相关联的分配器(allocator)的一个副本。

该方法是const成员函数,返回类型是allocator_type(即模板参数Allocator,默认tbb_allocator<std::pair<const Key, T>>),按值返回副本而非引用——这是 C++ 标准库无序容器观察者的通用约定,避免暴露容器内部状态句柄。

2.2 源码实现

基类concurrent_unordered_base中的实现非常简洁(include/oneapi/tbb/detail/_concurrent_unordered_base.h):

allocator_type get_allocator() const noexcept { return my_segments.get_allocator(); }

容器内部通过分桶段(segments)管理内存,my_segments是段表对象,其持有真正的分配器实例。get_allocator()直接透传该分配器的副本,并且被标记为noexcept——因为分配器的查询不涉及内存分配或可能抛异常的操作。

2.3 典型使用场景

  1. 复制构造语义:官方文档 construction_destruction_copying.rst 明确规定,复制构造函数未显式提供分配器时,会通过std::allocator_traits<allocator_type>::select_on_container_copy_construction(other.get_allocator())取得新容器的分配器;移动构造则调用std::move(other.get_allocator())。也就是说,get_allocator()是容器复制/移动操作内部配置传播的关键一环。

  2. 外部复用配置:当你需要创建一组"配置一致"的并发容器(例如同一批线程各自持有同分配器的 map)时,可以直接取回分配器副本再传给构造函数:

using Map = oneapi::tbb::concurrent_unordered_map<std::string, int>; Map m1(/* bucket_count */ 64, /* hash */ Map::hasher{}, /* equal */ Map::key_equal{}, MyAllocator{}); // 复用 m1 的分配器构造新容器 Map m2(m1.get_allocator()); // 或复制构造(分配器由 select_on_container_copy_construction 决定) Map m3(m1);
  1. 内存使用统计:当Allocator是有状态的统计型分配器时,通过get_allocator()取回实例可以读取累计分配/释放次数等状态。仓库测试 conformance_concurrent_vector.cpp 展示了类似的统计型分配器用法(items_allocated、items_freed等计数)。

  2. 节点句柄校验:node_type(节点句柄)也提供get_allocator(),测试 node_handling_support.h 中通过nh.get_allocator() == test_table.get_allocator()校验节点句柄与容器分配器一致,这是使用unsafe_extract/insert(node_type&&)组合时保证内存来源正确的惯用检查。

2.4 并发安全说明

与查找(Lookup)、并发安全修改(Concurrently safe modifiers)不同,observers.rst本身并未承诺观察者在并发写下的线程安全边界;从语义上讲这些查询是只读的,但按 oneTBB 文档惯例,涉及容器整体结构的并发操作仍应遵循容器自身的线程安全模型(并发插入/查找/遍历安全,unsafe_前缀操作不并发安全)。

三、hash_function():获取哈希函数副本

3.1 接口语义

hasher hash_function() const;

Returns:返回与*this相关联的哈希函数(hash function)的一个副本。

hasher即模板参数Hash,默认std::hash<Key>。该函数返回的哈希函数与容器内部用于确定元素所属桶编号的函数完全一致。

3.2 源码实现链路

concurrent_unordered_map的 traits 中定义了hash_compare_type = d1::hash_compare<Key, Hash, KeyEqual>(见 concurrent_unordered_map.h)。哈希与相等性被组合封装在hash_compare适配器中,基类实现如下(include/oneapi/tbb/detail/_concurrent_unordered_base.h):

hasher hash_function() const { return my_hash_compare.hash_function(); }

而hash_compare内部(include/oneapi/tbb/detail/_hash_compare.h)保存成员my_hasher与my_equal,并同样按值返回副本:

hasher hash_function() const { return my_hasher; } key_equal key_eq() const { return my_equal; }

调用链可概括为:

concurrent_unordered_map::hash_function() └─> concurrent_unordered_base::hash_function() // 透传 └─> hash_compare::hash_function() // 返回 my_hasher 副本

3.3 透明哈希与泛型重载

hash_compare还提供了模板化重载(include/oneapi/tbb/detail/_hash_compare.h):

template <typename K, typename = typename std::enable_if<is_transparent_hash::value, K>::type> std::size_t operator()( const K& key ) const { return std::size_t(my_hasher(key)); }

当Hash定义了嵌套类型transparent_key_equal(且其内部声明is_transparent)时,is_transparent_hash为std::true_type,hash_compare即可接受与Key不同类型的键——这就是find("literal")、contains(K)等透明查找操作无需构造临时Key对象的底层机制(详见 lookup.rst 中关于hasher::transparent_key_equal的参与条件说明)。该特质检测实现在 _containers_helpers.h:

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_equal<Key, Hasher, KeyEqual, tbb::detail::void_t<typename Hasher::transparent_key_equal>> : std::true_type { ... };

3.4 使用场景

  • 复现桶分布:自行对某个键计算hash_function()(key) % unsafe_bucket_count(),可预测该键将落入哪个桶,用于调试与桶接口(unsafe_begin(n)/unsafe_bucket(key),见类文档中的 Bucket interface 小节)配合分析冲突。
  • 一致性验证:在测试或日志中确认容器确实使用了预期的哈希函数(例如自定义了分片/加盐哈希后,通过hash_function()返回的实例做抽检验证)。

四、key_eq():获取键相等性谓词副本

4.1 接口语义

key_equal key_eq() const;

Returns:返回与*this相关联的键相等性判定谓词(key equality predicate)的一个副本。

注意这里的key_equal并不总是直接等于模板参数KeyEqual,其解析规则在类文档 concurrent_unordered_map_cls.rst 中有明确规定:

  • 若Hash::transparent_key_equal有效且表示一个类型,则key_equal取该限定名指向的类型;此时若模板参数KeyEqual不是std::equal_to<Key>,或Hash::transparent_key_equal::is_transparent无效/不表示类型,程序为 ill-formed(编译错误)。
  • 否则,key_equal即模板参数KeyEqual(默认std::equal_to<Key>)。

4.2 源码实现

基类同样做一层透传(include/oneapi/tbb/detail/_concurrent_unordered_base.h):

key_equal key_eq() const { return my_hash_compare.key_eq(); }

hash_compare的key_equal类型定义为typename is_transparent_hash::type(include/oneapi/tbb/detail/_hash_compare.h),与上面第三节的透明哈希检测保持一致:透明哈希启用时该类型为Hash::transparent_key_equal,否则为KeyEqual。因此key_eq()返回的谓词与容器内部用于键等价判断的谓词是同一实现。

4.3 使用场景

using Map = oneapi::tbb::concurrent_unordered_map<std::string, int>; Map m; auto eq = m.key_eq(); // 判断两个键在容器语义下是否等价(与容器内部判定一致) bool same = eq("key", "key");

当使用自定义KeyEqual(如大小写不敏感比较、近似比较)时,key_eq()可用于在容器外部复现"按容器规则的两个键是否等价"的判断,避免手工重复实现比较逻辑。

五、三个观察者的配套验证:测试证据

仓库测试 concurrent_unordered_common.h 明确覆盖了这两个哈希相关观察者:

// hasher hash_function() const; cont.hash_function(); // key_equal key_eq() const; cont.key_eq();

该公共测试头被concurrent_unordered_map、concurrent_unordered_multimap、concurrent_unordered_set、concurrent_unordered_multiset的测试共用;而get_allocator()则在 concurrent_associative_common.h(auto alloc = c.get_allocator();)与 container_move_support.h(移动构造/移动赋值场景下校验dst.get_allocator()与源容器分配器关系)中反复使用。这些测试从侧面印证了三个观察者在并发容器家族中的一致性契约:它们都返回"与容器当前配置严格一致"的副本。

六、观察者在并发容器家族中的通用性

观察者并非concurrent_unordered_map独有。搜索include/oneapi/tbb目录可以看到,get_allocator、hash_function、key_eq同样出现在concurrent_hash_map.h、concurrent_set.h、concurrent_unordered_set.h、concurrent_priority_queue.h、concurrent_queue.h等多个容器中;在参考文档目录doc/main/reference/source/containers/下,concurrent_map_cls/observers.rst、concurrent_set_cls/observers.rst、concurrent_multimap_cls/observers.rst、concurrent_unordered_multiset_cls/observers.rst等与本文主题页面并列存在。因此,理解concurrent_unordered_map的这三个观察者,也就掌握了 oneTBB 大多数并发容器观察者接口的通用模式:

观察者返回类型返回内容实现要点(concurrent_unordered_map)
get_allocator()allocator_type分配器副本透传my_segments.get_allocator(),noexcept
hash_function()hasher哈希函数副本经my_hash_compare.hash_function()返回my_hasher
key_eq()key_equal键相等谓词副本经my_hash_compare.key_eq()返回my_equal;透明哈希时类型变为Hash::transparent_key_equal

七、小结

concurrent_unordered_map的观察者方法虽然"体量小",却是容器配置可观测性的关键入口:

  • get_allocator()支撑复制/移动构造的分配器传播(select_on_container_copy_construction、std::move(other.get_allocator())),也可用于统计型分配器的状态读取;
  • hash_function()与key_eq()返回与容器内部完全一致的哈希与等价判定副本,配合透明哈希(transparent_key_equal)机制,可在容器外部精确复现桶分布与键等价语义。

三者共享同一套实现链路:类模板本身只定义 traits 与接口,真正的状态保存在基类concurrent_unordered_base的段表与hash_compare适配器中(分别见 _concurrent_unordered_base.h 与 _hash_compare.h),这保证了观察者返回值的"配置一致性"。在使用中建议遵循"只读查询、按值使用、必要时与查找/桶接口配合验证"的原则,即可安全高效地利用这三个接口。

  • 并发编程
  • 高性能计算

【免费下载链接】oneTBB

oneAPI Threading Building Blocks (oneTBB)

项目地址:https://gitcode.com/gh_mirrors/on/oneTBB
点击查看免费下载

相关推荐

上一篇:零延迟游戏串流:Sunshine自托管服务器完整配置指南
下一篇:一机多开:Nucleus Co-Op带你轻松搞定本地多人游戏分屏

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询