☰
oneTBB concurrent_hash_map 非成员二元比较运算符(operator== / operator!=)详解
2026/10/9 4:48:51 网站建设 项目流程
  • 并发编程
  • 高性能计算

【免费下载链接】oneTBB

oneAPI Threading Building Blocks (oneTBB)

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

导读

本文聚焦 oneAPI Threading Building Blocks(oneTBB)中oneapi::tbb::concurrent_hash_map的非成员二元比较运算符operator==与operator!=。作为该容器家族的标准比较接口,它们允许你在不修改容器内部结构的前提下,直接比较两个并发哈希表是否包含完全相同的键值对集合。读完本文,你将掌握这两个运算符的签名语义、相等判定条件、底层实现原理、模板参数约束,以及在实际并发编程场景中正确使用与测试它们的完整方案。

非成员比较运算符的作用

concurrent_hash_map是一个支持多线程并发读写的关联容器,其模板声明位于 include/oneapi/tbb/concurrent_hash_map.h,形式为:

template <typename Key, typename T, typename HashCompare, typename Allocator> class concurrent_hash_map;

其中:

  • Key表示键类型;
  • T表示映射值(mapped type)类型,容器元素类型为std::pair<const Key, T>;
  • HashCompare是哈希与相等比较策略,默认tbb_hash_compare<Key>;
  • Allocator是元素分配器。

与其他标准库关联容器类似,oneTBB 以**自由函数(非成员函数)**的形式为concurrent_hash_map提供了一对二元比较运算符。它们不访问容器的私有实现,而是通过公开接口(size()、迭代器、equal_range)完成判定,因此可以安全地用于任意两个同类型(或分配器不同的)concurrent_hash_map对象之间。

相等性的定义

官方参考文档 non_member_binary_comparisons.rst 明确规定,两个concurrent_hash_map对象相等当且仅当以下两个条件同时成立:

  1. 它们包含相等数量的元素(即size()相同);
  2. 一个容器中的每个元素在另一个容器中同样存在。

换言之,比较的是两个容器所承载的键值对集合内容,而不是内部桶的数量、布局、迭代顺序或分配器状态。即使两个表因并发插入历史不同而拥有完全不同的内部存储结构,只要它们的键值对集合一致,比较结果就为true。

这一点也体现在底层实现中:operator==的实现(见下文源码解析)在遍历时按键在另一个容器中重新查找,而不是依赖“位置对齐”的线性比对。

operator==:相等比较

签名

template <typename Key, typename T, typename HashCompare, typename Allocator> bool operator==( const concurrent_hash_map<Key, T, HashCompare, Allocator>& lhs, const concurrent_hash_map<Key, T, HashCompare, Allocator>& rhs );

返回:若lhs与rhs等价则返回true,否则返回false。

判定流程

在仓库的实际实现中(include/oneapi/tbb/concurrent_hash_map.h),operator==按以下步骤执行:

template <typename Key, typename T, typename HashCompare, typename A1, typename A2> inline bool operator==(const concurrent_hash_map<Key, T, HashCompare, A1> &a, const concurrent_hash_map<Key, T, HashCompare, A2> &b) { if(a.size() != b.size()) return false; typename concurrent_hash_map<Key, T, HashCompare, A1>::const_iterator i(a.begin()), i_end(a.end()); typename concurrent_hash_map<Key, T, HashCompare, A2>::const_iterator j, j_end(b.end()); for(; i != i_end; ++i) { j = b.equal_range(i->first).first; if( j == j_end || !(i->second == j->second) ) return false; } return true; }

该实现揭示了几点重要事实:

  1. 快速失败:先比较size(),若元素个数不同则直接返回false,避免无谓的全量遍历。
  2. 按键查找:对a中的每个元素,通过b.equal_range(i->first).first在b中定位同键元素。equal_range返回std::pair<iterator, iterator>,其first是匹配键的迭代器。
  3. 映射值比较:若在b中找不到对应键(j == j_end),或找到但映射值不相等(!(i->second == j->second)),则返回false。
  4. 元素比较前提:比较结果依赖于T(mapped type)是否提供operator==。因此,当T是自定义类型时,你必须为该类型实现(或由编译器默认合成)operator==,否则无法直接比较两个 map。

operator!=:不等比较

签名

template <typename Key, typename T, typename HashCompare, typename Allocator> bool operator!=( const concurrent_hash_map<Key, T, HashCompare, Allocator>& lhs, const concurrent_hash_map<Key, T, HashCompare, Allocator>& rhs );

返回:若lhs与rhs不相等则返回true,否则返回false。

语义与实现

operator!=严格等价于!(lhs == rhs),即对相等性取反。在源码中(include/oneapi/tbb/concurrent_hash_map.h),它被实现为:

#if !__TBB_CPP20_COMPARISONS_PRESENT template <typename Key, typename T, typename HashCompare, typename A1, typename A2> inline bool operator!=(const concurrent_hash_map<Key, T, HashCompare, A1> &a, const concurrent_hash_map<Key, T, HashCompare, A2> &b) { return !(a == b); } #endif // !__TBB_CPP20_COMPARISONS_PRESENT

注意这里的编译期宏__TBB_CPP20_COMPARISONS_PRESENT:在支持 C++20 三路比较推导(rewritten candidates)的编译环境中,operator!=可以由operator==自动推导生成,因此 oneTBB 不再重复定义operator!=,以避免重复声明与重载决议歧义。在 C++20 之前的标准下,operator!=则被显式定义为对operator==的取反。

模板参数与约束小结

  • 键与映射值类型:Key、T决定元素类型std::pair<const Key, T>;比较时键按HashCompare的哈希桶查找,映射值按T::operator==判定。
  • HashCompare:两个被比较对象的HashCompare类型必须一致(签名与实现均要求HashCompare相同),这是保证两个容器对“键相等”的理解一致的前提。
  • 分配器可以不同:值得注意的细节是,官方文档签名将两个操作数的分配器都写作Allocator,而实际实现则放宽为A1、A2两个独立的分配器模板参数(见上文源码)。这意味着分配器类型不同的两个concurrent_hash_map也可以直接比较,前提是二者的键值对集合一致。仓库测试 test/tbb/test_concurrent_hash_map.cpp 中的 "swap with NotAlwaysEqualAllocator allocators" 用例正是在自定义分配器NotAlwaysEqualAllocator下执行CHECK(map1 == map3)来验证该能力。
  • 比较结果不受内部状态影响:判定只取决于公开接口返回的元素集合,不涉及桶数组大小、增长历史等内部实现细节。

完整可运行示例

以下示例演示如何在项目中使用这两个运算符(假设包含头文件oneapi/tbb/concurrent_hash_map.h):

#include <oneapi/tbb/concurrent_hash_map.h> #include <iostream> #include <string> int main() { using map_t = oneapi::tbb::concurrent_hash_map<std::string, int>; map_t a, b, c; a.emplace("alpha", 1); a.emplace("beta", 2); b.emplace("beta", 2); b.emplace("alpha", 1); // 插入顺序不同 c.emplace("alpha", 1); // 缺少 "beta",且多一个 "gamma" c.emplace("gamma", 3); std::cout << std::boolalpha; std::cout << "a == b : " << (a == b) << '\n'; // true:键值对集合一致 std::cout << "a != c : " << (a != c) << '\n'; // true:元素集合不同 std::cout << "a != b : " << (a != b) << '\n'; // false:等价于 !(a == b) // 键相同但映射值不同 → 不相等 map_t d; d.emplace("alpha", 999); std::cout << "a == d : " << (a == d) << '\n'; // false return 0; }

输出预期:

a == b : true a != c : true a != b : false a == d : false

上述行为与相等性定义完全一致:a与b虽插入顺序不同,但元素数量相同且每个元素在对方容器中都能找到,故相等;c与a的元素集合不同,故不相等;d中"alpha"的映射值与a不同,故不相等。

并发场景下的使用注意事项

  1. 比较是“快照式”的:operator==通过迭代器遍历容器。虽然concurrent_hash_map的迭代器在并发写入下依然安全,但比较两个正在被其他线程修改的 map 时,结果反映的是比较执行过程中的某一状态视图,不应假定其代表某个精确时间点的原子快照。若需要严格一致的比较,应在无并发写入的窗口期执行,或自行加锁协调。
  2. 性能特征:实现先比较size()做快速失败,随后对每个元素执行一次对另一容器的equal_range查找,总体复杂度约为 O(n)(n 为元素个数),取决于HashCompare的哈希质量与桶负载。
  3. 映射值的 operator== 必须可用:对于自定义T,若未提供operator==,operator==的实例化将编译失败。参考测试 test/conformance/conformance_concurrent_hash_map.cpp 中MyData的做法——它显式定义了bool operator==( const MyData& ) const供元素比较使用。
  4. 同族容器一致性:operator!=与operator==满足互补关系(a != b恒等于!(a == b)),在 C++20 环境下该关系由语言层面的比较推导保证,在 C++11/14/17 下由 oneTBB 的显式实现保证。

与同族容器的对比

oneTBB 的其他并发容器也提供了类似语义的非成员比较运算符,便于理解本接口的设计一致性:

  • concurrent_queue的operator==比较unsafe_size()与元素序列(include/oneapi/tbb/concurrent_queue.h);
  • concurrent_priority_queue的operator==直接比较底层数据(include/oneapi/tbb/concurrent_priority_queue.h)。

它们的operator!=同样在非 C++20 环境下定义为!(lhs == rhs)。相比之下,concurrent_hash_map的比较关注“键值对集合”而非序列顺序,这与其无序哈希容器的本质一致。

小结

  • operator==判定两个concurrent_hash_map元素数量相等且键值对集合一致;operator!=为其逻辑取反。
  • 实现上先比较size()快速失败,再按键在对方容器中equal_range查找并比较映射值(include/oneapi/tbb/concurrent_hash_map.h)。
  • 比较不依赖内部布局、插入顺序或分配器类型(分配器不同仍可比较)。
  • 使用前提是T可比较(提供operator==),且两个对象的HashCompare类型一致。
  • 在 C++20 下operator!=由语言推导,在旧标准下由 oneTBB 显式定义为!(a == b)。
  • 并发编程
  • 高性能计算

【免费下载链接】oneTBB

oneAPI Threading Building Blocks (oneTBB)

项目地址:https://gitcode.com/gh_mirrors/on/oneTBB
点击查看免费下载
上一篇:FanControl终极配置指南:华硕主板传感器兼容性快速解决
下一篇:终极DALL-E 2 PyTorch模型并行方案:多GPU环境下的高效层拆分技术指南 🚀

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

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

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

立即咨询