oneAPI TBBconcurrent_map观察者(Observers)接口详解:get_allocator、key_comp与value_comp
【免费下载链接】moldmold: A Modern Linker 🦠项目地址: https://gitcode.com/GitHub_Trending/mo/mold
导读
本文聚焦 oneAPI Threading Building Blocks(oneTBB)concurrent_map容器中一个短小精悍但极易被忽略的接口子集——观察者(Observers)成员函数,完整讲解get_allocator()、key_comp()、value_comp()三个方法的语义、签名、返回值与底层实现,并顺带剖析其辅助类型value_compare的类结构。内容以 observers.rst 规范文档为主体骨架,结合仓库内 concurrent_map.h 与 _concurrent_skip_list.h 的源码实现展开。读完本文,你将掌握:三个观察者接口各自返回什么、在何种场景下使用、底层是如何实现的,以及如何基于value_comp()实现有序遍历的合法性校验。
观察者接口:只读、无副作用的查询方法
在 oneTBB 容器家族中,"观察者(Observers)"指的是只读取容器内部配置、不修改容器内容的成员函数。concurrent_map的观察者共有三个,全部定义于规范文档 observers.rst:
| 成员函数 | 返回类型 | 返回内容 |
|---|---|---|
get_allocator() | allocator_type | 与*this关联的分配器的副本 |
key_comp() | key_compare | 与*this关联的键比较函数对象的副本 |
value_comp() | value_compare | 用于比较value_type对象的value_compare对象 |
三个方法均为const成员函数(签名带const限定),意味着它们不改变容器状态,可安全地在只读上下文中调用。其中get_allocator与key_comp的语义与 C++ 标准库std::map完全对齐,便于从标准容器迁移的开发者无缝衔接。
get_allocator():获取关联分配器
接口签名
allocator_type get_allocator() const;返回值:与*this关联的分配器(allocator)的副本。
语义与源码实现
concurrent_map的模板签名默认使用 oneTBB 自带分配器:
template <typename Key, typename Value, typename Compare = std::less<Key>, typename Allocator = tbb::tbb_allocator<std::pair<const Key, Value>>> class concurrent_map;该声明位于 concurrent_map.h。concurrent_map继承自内部实现类concurrent_skip_list,get_allocator()的具体实现位于跳表基类中:
allocator_type get_allocator() const { return my_node_allocator; }参见 _concurrent_skip_list.h。它直接返回容器内部持有的节点分配器my_node_allocator的副本。
tbb_allocator本身定义于 tbb_allocator.h,其allocate()/deallocate()分别委托给 oneTBB 运行时库的r1::allocate_memory/r1::deallocate_memory。若程序运行时链接了 TBB malloc 库,allocator_type()静态方法会返回scalable,否则返回standard——这为开发者提供了一种运行时检测"当前是否走可扩展内存分配器"的手段。
典型使用场景
- 在容器构造/拷贝时,用
get_allocator()将同一个分配器传递给其他容器,保证内存池共享; - 在
select_on_container_copy_construction语义下构造副本容器(源码中concurrent_skip_list的拷贝构造即使用了other.get_allocator(),见 _concurrent_skip_list.h); - 与
node_type节点句柄配合:TBB 测试套件中会校验节点句柄的分配器与容器一致(见 node_handling_support.h)。
key_comp():获取键比较器
接口签名
key_compare key_comp() const;返回值:与*this关联的键比较函数对象(key comparison functor)的副本。
语义与源码实现
key_compare即模板参数Compare的别名(using key_compare = Compare;,见 concurrent_map.h)。默认情况下它是std::less<Key>。
实现同样位于跳表基类:
key_compare key_comp() const { return my_compare; }参见 _concurrent_skip_list.h。容器内部在构造时通过构造函数参数保存比较器对象my_compare(源码第 1260 行处声明),key_comp()只是把它按值返回。
典型使用场景
- 获取容器当前的排序规则,用于对键做与容器一致的独立排序(例如构造一个
std::set或排序辅助结构); - 配合
value_comp()实现有序遍历校验(见下文); - 与
std::map的key_comp()行为完全一致,便于迁移。
value_comp()与value_compare:比较完整键值对
接口签名
value_compare value_comp() const;返回值:一个value_compare类对象,用于比较value_type(即std::pair<const Key, T>)对象。
value_compare类结构
concurrent_map::value_compare是一个函数对象(functor),它通过比较value_type的第一个分量(即键)来比较两个键值对。规范文档 value_compare_cls.rst 给出了完整类纲:
namespace oneapi { namespace tbb { template <typename Key, typename T, typename Compare, typename Allocator> class concurrent_map<Key, T, Compare, Allocator>::value_compare { protected: key_compare comp; value_compare( key_compare c ); public: bool operator()( const value_type& lhs, const value_type& rhs ) const; }; // class value_compare } // namespace tbb } // namespace oneapi成员对象:key_compare comp;——存储的键比较函数对象。
成员函数:
value_compare( key_compare c );以给定的键比较函数对象c构造value_compare(该构造函数为protected,用户一般通过value_comp()间接获得实例)。
bool operator()( const value_type& lhs, const value_type& rhs ) const;通过调用存储的键比较函数comp比较lhs.first与rhs.first。返回值:当lhs与rhs的第一分量(键)相等时返回true,否则返回false。
源码级实现
仓库中的实际实现位于map_traits内嵌类(concurrent_map.h):
class value_compare { public: bool operator()(const value_type& lhs, const value_type& rhs) const { return comp(lhs.first, rhs.first); } protected: value_compare(compare_type c) : comp(c) {} friend struct map_traits; compare_type comp; };而value_comp()的构造逻辑为:
static value_compare value_comp(compare_type comp) { return value_compare(comp); }(见 concurrent_map.h)
跳表基类中的转发调用为:
value_compare value_comp() const { return container_traits::value_comp(my_compare); }(见 _concurrent_skip_list.h)
从源码结构可以看出:value_comp()并非独立保存一份比较器,而是每次调用时基于容器持有的my_compare现场构造一个轻量的value_compare包装对象,其内部仍指向同一个键比较器。因此它几乎没有额外的内存开销。
实战:基于观察者校验concurrent_map的有序性
TBB 测试套件 concurrent_ordered_common.h 给出了观察者接口的经典实战用法——校验有序容器的遍历顺序:
template <typename Container> void check_container_order( const Container& cont ) { if (!cont.empty()) { typename Container::key_compare key_comp = cont.key_comp(); typename Container::value_compare value_comp = cont.value_comp(); OrderChecker<Container> check_order(value_comp, key_comp); for (auto it = cont.begin(); std::next(it) != cont.end();) { auto pr_it = it++; REQUIRE_MESSAGE(check_order(*pr_it, *it), "The order of the elements is broken"); } } }这里正是通过cont.key_comp()与cont.value_comp()取得与容器一致的比较规则,再对相邻元素两两验证"后一个元素不小于前一个元素",从而断言容器按预期有序。OrderChecker内部对普通容器使用val_comp(lhs, rhs) && key_comp(...)严格序判定,对多元素容器则额外放宽为"不大于"(源码第 50-53 行的!val_comp(rhs, lhs) && !key_comp(...)分支)。
这一用法也可以直接移植到业务代码中:当你在多线程环境下向concurrent_map写入大量数据后,可在只读阶段用观察者接口验证数据完整性,或基于同一比较器对键集合做二次处理。
补充:观察者在concurrent_multimap中的一致性
观察者接口并非concurrent_map独有。同一定义文件中的concurrent_multimap(允许重复键的有序并发关联容器,见 concurrent_map.h)同样通过继承concurrent_skip_list获得相同的get_allocator、key_comp、value_comp行为,且value_compare对value_type的比较规则完全一致。这意味着本文所述全部接口语义,对concurrent_map与concurrent_multimap一并成立。
小结
| 观察者 | 返回内容 | 底层实现位置 |
|---|---|---|
get_allocator() | 关联分配器副本 | _concurrent_skip_list.hL675 |
key_comp() | 键比较器副本 | _concurrent_skip_list.hL705 |
value_comp() | value_compare包装对象 | _concurrent_skip_list.hL707,构造逻辑在concurrent_map.hL61 |
三个观察者方法共同构成了concurrent_map对外暴露"容器内部策略"的唯一只读窗口:分配器决定了内存来源,比较器决定了元素排序与相等语义。理解它们,是编写可移植、可验证的并发有序容器代码的基础。
【免费下载链接】moldmold: A Modern Linker 🦠项目地址: https://gitcode.com/GitHub_Trending/mo/mold
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考