资讯动态

深入解析 tbb::concurrent_map 迭代器:begin/cbegin/end/cend 的语义、ForwardIterator 要求与源码实现(mold 仓库 oneTBB 实践)

发布时间:2026/9/14 20:36:51 来源:尧图企业网站定制
深入解析 tbb::concurrent_map 迭代器begin/cbegin/end/cend 的语义、ForwardIterator 要求与源码实现mold 仓库 oneTBB 实践【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold导读本文以 mold 仓库所集成的 oneTBB 官方规范文档 iterators.rst 为骨架系统讲解tbb::concurrent_map迭代器体系iterator与const_iterator两个成员类型的定义、begin/cbegin/end/cend六个成员函数的精确签名与返回值语义并下沉到跳表实现源码与一致性测试揭示迭代器“按键有序、可并发读”的底层机制。读完本文你将掌握在并发场景下安全遍历concurrent_map、区分只读与可写迭代器、以及与std::算法和parallel_for_each配合使用的完整实战方案。一、文档背景concurrent_map 的迭代器类型oneTBB 规范文档 iterators.rst 明确指出类型concurrent_map::iterator与concurrent_map::const_iterator满足 ISO C 标准 [forward.iterators] 一节对ForwardIterator前向迭代器的全部要求。这意味着这两类迭代器具备前向迭代器的完整能力可多次解引用、可自增it、支持默认构造、支持相等比较但不保证随机访问不支持it n、it[n]等操作。从源码结构看tbb::concurrent_map实际上是基于跳表skip list实现的。在 concurrent_map.h 中可以看到template typename Key, typename Value, typename Compare std::lessKey, typename Allocator tbb::tbb_allocatorstd::pairconst Key, Value class concurrent_map : public concurrent_skip_list map_traitsKey, Value, Compare, geometric_level_generator32, Allocator, false { using base_type concurrent_skip_list...; public: using key_type Key; using mapped_type Value; using value_type typename base_type::value_type; using iterator typename base_type::iterator; // 继承自跳表迭代器 using const_iterator typename base_type::const_iterator; // 继承自跳表迭代器 ... };即concurrent_map直接复用concurrent_skip_list底层随机层高生成器为geometric_level_generator32的迭代器类型自身并不单独定义迭代器类。理解这一点就能顺藤摸瓜找到真正的迭代器实现skip_list_iterator位于 _concurrent_skip_list.h。二、begin 与 cbegin获取首元素迭代器规范文档给出了三个“取头”函数的精确签名iterator begin(); const_iterator begin() const; const_iterator cbegin() const;返回值语义返回指向容器中第一个元素的迭代器。若容器为空则返回值等价于end()。2.1 源码级实现对应实现位于 _concurrent_skip_list.hiterator begin() { return iterator(internal_begin()); } const_iterator begin() const { return const_iterator(internal_begin()); } const_iterator cbegin() const { return const_iterator(internal_begin()); }而internal_begin()的语义是“取跳表第 0 层最底层链表的第一个真实数据节点”node_ptr internal_begin() const { node_ptr head get_head(); return head nullptr ? head : head-next(0); }这里可以提炼两个关键实现事实跳表的有序性由第 0 层单向链表承载。跳表的高层level 1 及以上只用于加速查找真正保存“全部元素、按key_compare排序”的线性序列是 level 0 链。因此迭代器按 key 的升序遍历容器——这是concurrent_map迭代器与concurrent_hash_map无序最本质的区别。空容器下begin()直接退化为“空指针迭代器”与end()的表示一致从而保证begin() end()的区间为空。2.2 三个重载的选型建议调用场景建议函数返回类型非 const 对象、需要修改 valuebegin()iteratorconst 对象上调用begin() constconst_iterator显式强调只读意图cbegin()const_iterator注意与std::map一致concurrent_map的iterator解引用得到的是std::pairconst Key, Valuekey 恒为 const只有mapped_typeValue可修改而const_iterator解引用得到const std::pairconst Key, Valuevalue 也不可改。三、end 与 cend获取尾后迭代器规范文档给出的“取尾”函数签名iterator end(); const_iterator end() const; const_iterator cend() const;返回值语义返回指向容器最后一个元素之后位置past-the-end的迭代器即尾后迭代器。对end()/cend()解引用或自增是未定义行为它只用于区间终止判断。3.1 源码级实现_concurrent_skip_list.hiterator end() { return iterator(nullptr); } const_iterator end() const { return const_iterator(nullptr); } const_iterator cend() const { return const_iterator(nullptr); }可以看到尾后迭代器在内部就是一个持有空节点指针nullptr的哨兵。配合skip_list_iterator的自增操作my_node_ptr my_node_ptr-next(0)当迭代器越过最后一个节点后next(0)返回nullptr迭代器自然“落”到end()位置构成标准的前向迭代区间[begin(), end())。四、ForwardIterator 要求的实现印证规范文档强调迭代器满足 [forward.iterators] 要求这一承诺在 skip_list_iterator 的实现中逐项落地template typename NodeType, typename ValueType class skip_list_iterator { using node_type NodeType; using node_ptr node_type*; public: using iterator_category std::forward_iterator_tag; // 类别标签前向迭代器 using value_type ValueType; using difference_type std::ptrdiff_t; using pointer value_type*; using reference value_type; skip_list_iterator() : skip_list_iterator(nullptr) {} // 可默认构造 reference operator*() const { return my_node_ptr-value(); } // 可解引用 pointer operator-() const { return my_node_ptr-storage(); } skip_list_iterator operator() { // 前置自增 __TBB_ASSERT(my_node_ptr ! nullptr, nullptr); my_node_ptr my_node_ptr-next(0); // 沿 level 0 前进 return *this; } skip_list_iterator operator(int) { // 后置自增 skip_list_iterator tmp *this; *this; return tmp; } ... }; friend bool operator( const skip_list_iterator lhs, const skip_list_iterator rhs ) { return lhs.my_node_ptr rhs.my_node_ptr; } friend bool operator!( const skip_list_iterator lhs, const skip_list_iterator rhs ) { return lhs.my_node_ptr ! rhs.my_node_ptr; }逐一对照 ForwardIterator 要求iterator_category为std::forward_iterator_tag明确宣告迭代器类别可默认构造skip_list_iterator()以空指针初始化可解引用、可取成员operator*/operator-直接作用于节点内嵌的value_type可自增前置/后置两种形式齐备且自增是“沿 level 0 链前进一个节点”可相等比较operator/operator!基于底层节点指针比较多次自增后仍可解引用多遍遍历语义因为遍历的是静态的 level 0 链表同一迭代器可重复遍历同一序列。由于解引用、自增等操作都直接作用在节点指针上整个前向遍历几乎只读内存、不触碰全局锁或原子计数因此多个线程可以同时遍历同一个concurrent_map而不互相阻塞。五、const_iterator 的隐式转换与常量正确性skip_list_iterator还提供了一个值得注意的转换构造_concurrent_skip_list.hskip_list_iterator( const skip_list_iteratornode_type, typename node_type::value_type other ) : my_node_ptr(other.my_node_ptr) {}即非 const 的iterator可以隐式转换为const_iterator与标准库容器行为一致。这带来两个实用结论concurrent_map的非 const 对象可以直接把begin()的结果传给以const_iterator为形参的算法或函数无需显式转换反过来const_iterator→iterator不允许从而保证 const 对象无法通过迭代器修改 value。一致性测试 conformance_concurrent_map.cpp 用编译期断言锁定了这些契约static_assert(utils::is_forward_iteratortypename container_type::iterator::value, Incorrect container iterator member type); static_assert(!std::is_consttypename container_type::iterator::value_type::value, Incorrect container iterator member type); static_assert(utils::is_forward_iteratortypename container_type::const_iterator::value, Incorrect container const_iterator member type); static_assert(std::is_consttypename container_type::const_iterator::value_type::value, Incorrect container const_iterator member type);即iterator与const_iterator都必须是前向迭代器iterator::value_type非 constvalue 可改const_iterator::value_type为 const完全只读。六、实战示例遍历 concurrent_map6.1 标准遍历写法#include tbb/concurrent_map.h #include iostream int main() { tbb::concurrent_mapstd::string, int scores; scores.emplace(alice, 90); scores.emplace(bob, 85); scores.emplace(carol, 95); // 方式一范围 for内部即 begin()/end() for (auto [name, score] : scores) std::cout name : score \n; // 方式二显式迭代器循环 for (auto it scores.begin(); it ! scores.end(); it) std::cout it-first : it-second \n; // 方式三const 上下文使用 cbegin/cend const auto cscores scores; for (auto it cscores.cbegin(); it ! cscores.cend(); it) std::cout it-first \n; return 0; }三种写法输出顺序一致由于底层是跳表 level 0 有序链遍历结果总是按key_compare默认std::less升序排列即 alice → bob → carol。6.2 与标准库算法协作因为满足 ForwardIteratorbegin()/end()可以直接喂给std::算法#include algorithm // 统计总分 int total 0; std::for_each(scores.begin(), scores.end(), { total kv.second; }); // 找出第一个分数 90 的条目 auto it std::find_if(scores.begin(), scores.end(), [](const auto kv) { return kv.second 90; }); if (it ! scores.end()) std::cout it-first \n;6.3 只修改 value、不触碰 key规范文档未单独提供按键查找遍历但结合容器 API 的惯用法是先find/insert再通过迭代器写 valueauto it scores.find(bob); if (it ! scores.end()) it-second 5; // key 为 constvalue 可改七、从顺序遍历到并行迭代range() 的衔接规范文档 iterators.rst 是concurrent_map迭代器章节的一部分与之配套的 parallel_iteration.rst 定义了并行迭代接口range_type range(); const_range_type range() const;其中range_type的区间边界就是concurrent_map::iteratorconst_range_type的边界则是concurrent_map::const_iterator——并行迭代与本文介绍的顺序迭代共享同一套迭代器类型。实现位于 _concurrent_skip_list.hclass const_range_type { ... iterator begin() const; iterator end() const; ... const_iterator my_begin; const_iterator my_end; size_type my_level; }; class range_type : public const_range_type { ... }; range_type range() { return range_type(*this); } const_range_type range() const { return const_range_type(*this); }配合 oneTBB 并行算法即可安全并发遍历只读场景#include tbb/parallel_for_each.h tbb::parallel_for_each(scores.range(), [](const auto kv) { // 并发只读遍历每线程分得一段有序区间 process(kv.first, kv.second); });八、并发遍历的注意事项从实现可推断的边界结合跳表迭代器的实现结构可以谨慎归纳以下并发使用边界遍历期间允许其他线程并发插入/删除。迭代器只保存一个节点指针自增仅沿 level 0 链前进不依赖全局状态但规范文档并未承诺“遍历看到的是某一时刻的一致快照”因此不要假设遍历结果反映固定的容器状态也不要在并发修改下依赖size()与遍历计数严格一致。遍历与unsafe_erase/unsafe_extract的互斥责任在调用方。从unsafe_前缀可知这些修改接口与正在进行的遍历之间需要外部同步安全修改接口如erase、insert的线程安全版本可在遍历的同时安全调用但迭代器指向的元素可能已被并发线程删除解引用前需自行保证元素仍存在。禁止通过迭代器修改 key。value_type是std::pairconst Key, Value编译器层面已阻止改写 key若强行绕过如const_cast会破坏跳表有序性属于未定义行为。end()/cend()只用于区间比较对其解引用或自增无意义且危险。九、总结本文档虽然篇幅精炼但给出了concurrent_map迭代器体系的完整契约iterator/const_iterator均满足 ISO C ForwardIterator 要求begin/cbegin返回首元素迭代器、end/cend返回空指针哨兵表示的尾后迭代器。通过对照 concurrent_map.h 与 _concurrent_skip_list.h 的源码我们确认了这些 API 背后的三个关键事实迭代器遍历的是跳表level 0 有序链因此输出天然按键升序尾后迭代器即nullptr哨兵前向自增到链表尾部后自然与end()相等迭代器操作无锁、只读内存多线程可并发只读遍历并可与range()/parallel_for_each无缝衔接。对实现细节的进一步验证可参考一致性测试 conformance_concurrent_map.cpp其中以static_assert固化了“两类迭代器均为前向迭代器、const 属性正确”的编译期保证。掌握这套迭代器语义是在并发程序中正确、高效使用tbb::concurrent_map的基础。【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价