- 并发编程
- 高性能计算
【免费下载链接】oneTBB
oneAPI Threading Building Blocks (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对象相等当且仅当以下两个条件同时成立:
- 它们包含相等数量的元素(即
size()相同); - 一个容器中的每个元素在另一个容器中同样存在。
换言之,比较的是两个容器所承载的键值对集合内容,而不是内部桶的数量、布局、迭代顺序或分配器状态。即使两个表因并发插入历史不同而拥有完全不同的内部存储结构,只要它们的键值对集合一致,比较结果就为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; }该实现揭示了几点重要事实:
- 快速失败:先比较
size(),若元素个数不同则直接返回false,避免无谓的全量遍历。 - 按键查找:对
a中的每个元素,通过b.equal_range(i->first).first在b中定位同键元素。equal_range返回std::pair<iterator, iterator>,其first是匹配键的迭代器。 - 映射值比较:若在
b中找不到对应键(j == j_end),或找到但映射值不相等(!(i->second == j->second)),则返回false。 - 元素比较前提:比较结果依赖于
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不同,故不相等。
并发场景下的使用注意事项
- 比较是“快照式”的:
operator==通过迭代器遍历容器。虽然concurrent_hash_map的迭代器在并发写入下依然安全,但比较两个正在被其他线程修改的 map 时,结果反映的是比较执行过程中的某一状态视图,不应假定其代表某个精确时间点的原子快照。若需要严格一致的比较,应在无并发写入的窗口期执行,或自行加锁协调。 - 性能特征:实现先比较
size()做快速失败,随后对每个元素执行一次对另一容器的equal_range查找,总体复杂度约为 O(n)(n 为元素个数),取决于HashCompare的哈希质量与桶负载。 - 映射值的 operator== 必须可用:对于自定义
T,若未提供operator==,operator==的实例化将编译失败。参考测试 test/conformance/conformance_concurrent_hash_map.cpp 中MyData的做法——它显式定义了bool operator==( const MyData& ) const供元素比较使用。 - 同族容器一致性:
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)
相关推荐
oneTBB concurrent_bounded_queue 非成员二元比较运算符(operator== / operator!=)解析
oneTBB concurrent_bounded_queue 非成员二元比较运算符(operator== / operator!=)解析 导读 oneapi:
并发编程高性能计算408考研12周备考路线图:14年真题考频统计告诉你先学什么
408考研12周备考路线图:14年真题考频统计告诉你先学什么 80%的分值藏在约30%的考点里,这是14年真题数出来的结论。本文只依据仓库文件 6其他资源/历年
文档教育教程RevokeMsgPatcher 完整安装教程:微信QQ防撤回补丁十分钟搞定
RevokeMsgPatcher 完整安装教程:微信QQ防撤回补丁十分钟搞定 RevokeMsgPatcher 是一款 Windows 平台的开源补丁工具,给
桌面应用即时通讯
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考