TBB concurrent_vector 迭代器实现详解RandomAccessIterator 语义、源码机制与实战用法【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold本文以 oneTBBoneAPI Threading Building Blocks规格文档中concurrent_vector容器的迭代器规范为主体完整梳理begin/end/rbegin/rend等接口族的语义与签名并结合 oneTBB 源码逐层拆解迭代器的三成员结构、指针缓存与段segment边界失效机制帮助读者既会正确使用这些迭代器也能理解随机访问迭代器在一个可并发增长的容器上是如何被高效且安全地实现的。规范出处与迭代器的核心承诺迭代器规范的原始文档位于 iterators.rst它与 concurrent_growth.rst、parallel_iteration.rst 等同目录文档共同构成concurrent_vector的完整规格。该文档给出的第一条也是最重要的承诺是The typesconcurrent_vector::iteratorandconcurrent_vector::const_iteratormeet the requirements ofRandomAccessIteratorfrom the [random.access.iterators] ISO C Standard section.也就是说concurrent_vector的正向迭代器同时满足 ISO C 标准的RandomAccessIterator要求因此天然也满足BidirectionalIterator和ForwardIterator的所有要求。从源码可以印证这一点concurrent_vector.h 中迭代器类别被显式定义为随机访问标签L51using iterator_category std::random_access_iterator_tag;这意味着你可以放心使用全部 STL 惯用法std::copy、std::for_each、std::sort、it n、it - n、a b、it[n]、std::next/std::prev等行为与std::vector::iterator一致。迭代器 API 全览begin / end / rbegin / rend规范文档定义了四组共 12 个成员函数其签名与返回值语义如下。begin 与 cbeginiterator begin(); const_iterator begin() const; const_iterator cbegin() const;返回值指向向量中第一个元素的迭代器。end 与 cenditerator end(); const_iterator end() const; const_iterator cend() const;返回值指向向量中最后一个元素之后位置的迭代器即惯用的“尾后迭代器”。rbegin 与 crbeginreverse_iterator rbegin(); const_reverse_iterator rbegin() const; const_reverse_iterator crbegin() const;返回值反向迭代器指向逆序向量中的第一个元素即原向量的最后一个元素。rend 与 crendreverse_iterator rend(); const_reverse_iterator rend() const; const_reverse_iterator crend() const;返回值反向迭代器指向逆序向量中最后一个元素之后的位置即原向量第一个元素之前的位置。对照源码这些函数体都只有短短一行实现位于 concurrent_vector.h 的 L462–L477// Iterators iterator begin() { return iterator(*this, 0); } const_iterator begin() const { return const_iterator(*this, 0); } const_iterator cbegin() const { return const_iterator(*this, 0); } iterator end() { return iterator(*this, size()); } const_iterator end() const { return const_iterator(*this, size()); } const_iterator cend() const { return const_iterator(*this, size()); } reverse_iterator rbegin() { return reverse_iterator(end()); } const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } const_reverse_iterator crbegin() const { return const_reverse_iterator(cend()); } reverse_iterator rend() { return reverse_iterator(begin()); } const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } const_reverse_iterator crend() const { return const_reverse_iterator(cbegin()); }可以看到几个实现要点迭代器本质上由“容器引用 下标”构造而来begin()是下标 0end()是下标size()反向迭代器直接用标准库适配器std::reverse_iterator包装正向迭代器得到类型定义在 L274–L277using iterator vector_iteratorconcurrent_vector, value_type; using const_iterator vector_iteratorconcurrent_vector, const value_type; using reverse_iterator std::reverse_iteratoriterator; using const_reverse_iterator std::reverse_iteratorconst_iterator;由于内层的vector_iterator满足随机访问要求经std::reverse_iterator包装后的reverse_iterator同样满足RandomAccessIterator要求因此反向迭代器也支持算术运算例如c.crend() - 1是合法的测试代码 test_concurrent_vector.cpp L298 就使用了*(c.cend()-1)与*c.crbegin()的等价比较。值得注意的一点end()中的size()是调用时刻的快照。源码中size()定义为L488–L490size_type size() const noexcept { return std::min(this-my_size.load(std::memory_order_acquire), capacity()); }由于concurrent_vector允许其他线程随时grow_by/push_back迭代区间[begin(), end())的语义是“构造迭代器那一刻的区间”。如果你在多线程环境下迭代建议先取一次size()或end()快照再迭代到该快照为止避免因容器持续膨胀导致循环边界不确定的情况。vector_iterator 的三成员结构索引 指针缓存从源码结构看随机访问能力建立在如下这个极简的三成员结构上concurrent_vector.h L168–L178private: // concurrent_vector over which we are iterating. vector_type* my_vector; // 被迭代的容器 // Index into the vector size_type my_index; // 逻辑下标 // Caches my_vector *it; // If my_item nullptr cached value is not available use internal_subscript(my_index) mutable value_type* my_item; // 元素指针缓存my_vector迭代器所依附的concurrent_vectormy_index元素在容器中的逻辑下标是迭代器身份的核心——所有比较和算术都只依赖它my_item一个可选的元素指针缓存。为nullptr时回退到通过internal_subscript(my_index)按段表查表取址非空时直接解引用省去一次查表。解引用操作符operator*展示了缓存的验证逻辑L111–L119reference operator*() const { value_type *item my_item; if (item nullptr) { item my_vector-internal_subscript(my_index); } else { __TBB_ASSERT(item my_vector-internal_subscript(my_index), corrupt cache); } return *item; }调试构建下它甚至会用断言校验缓存指针与真实地址是否一致防止缓存被“污染”。段Segment边界与缓存失效为什么迭代器不能只存一个指针这是理解concurrent_vector迭代器的关键。普通std::vector的全部元素在一段连续内存上迭代器持有一个裸指针即可而concurrent_vector为了支持无锁并发增长采用“段表”布局元素按指数增长的段存放第 0 段大小为 2第 k 段k ≥ 1大小为 2^k、起始下标为 2^k各段独立分配、可能位于完全不同的内存地址。段定位函数定义在 _segment_table.h L324–L336// Return the segment where index is stored static constexpr segment_index_type segment_index_of( size_type index ) { return size_type(tbb::detail::log2(uintptr_t(index|1))); } // Return size of the segment static constexpr size_type segment_size( size_type index ) { return index 0 ? 2 : size_type(1) index; }既然相邻元素在跨段时并不连续迭代器就必须知道“什么时候缓存指针会失效”。源码给出的判据非常精巧下标是 2 的幂且不小于 2的元素恰好是某一段的第一个元素L733–L736static constexpr bool is_first_element_in_segment( size_type index ) { // An element is the first in a segment if its index is equal to a power of two return is_power_of_two_at_least(index, 2); }前缀自增operator据此决定是否保留缓存L127–L139vector_iterator operator() { my_index; if (my_item ! nullptr) { if (vector_type::is_first_element_in_segment(my_index)) { // If the iterator crosses a segment boundary, the pointer become invalid // as possibly next segment is in another memory location my_item nullptr; } else { my_item; } } return *this; }若新下标仍是同一段内的元素直接my_item一次指针递增即可若新下标是段首2 的幂说明跨段了下一段可能在别的内存位置缓存指针作废置nullptr下次解引用时再查表重建若缓存本来就是nullptr则什么都不做保持惰性。operator--对称地处理了前向跨段的情况L147–L160并且用断言保证不会对begin()位置做前退vector_iterator operator--() { __TBB_ASSERT(my_index 0, operator--() applied to iterator already at beginning of concurrent_vector); ... }这个设计带来一个实际效果同段内的连续遍历几乎零开销只是指针加减跨段才付出一次查表代价——这是随机访问迭代器在“逻辑连续、物理分段”存储上的典型优化。算术与比较运算符支持 const/非 const 混合比较vector_iterator的模板参数是Vector, Value其中Value为T或const T因此iterator与const_iterator是“同一个类、不同实例化”。源码利用友元模板使得两种实例化之间可以直接运算例如L187–L208 节选template typename Vector, typename T, typename U typename vector_iteratorVector, T::difference_type operator-( const vector_iteratorVector, T i, const vector_iteratorVector, U j ) { using difference_type typename vector_iteratorVector, T::difference_type; return static_castdifference_type(i.my_index) - static_castdifference_type(j.my_index); } template typename Vector, typename T, typename U bool operator( const vector_iteratorVector, T i, const vector_iteratorVector, U j ) { return i.my_vector j.my_vector i.my_index j.my_index; }完整提供的运算符包括运算符说明it n、n it、it - n迭代器算术基于my_index加减it n、it - n原地移动同时作废my_item缓存it1 - it2返回difference_type下标之差、!、、、、全序比较同时比较容器指针与下标*it、it-m、it[n]解引用、成员访问、随机下标it、it、--it、it--双向移动含段边界缓存失效逻辑所有比较与差值都只依赖my_index不遍历、不查段表因此比较是 O(1) 的。实战示例以下示例完整覆盖了 begin/end/cbegin/cend/rbegin/rend/crbegin/crend 的使用可配合任意标准库算法#include tbb/concurrent_vector.h #include algorithm #include iostream #include iterator int main() { tbb::concurrent_vectorint cv; for (int i 1; i 100; i) { cv.push_back(i); // 注意push_back 返回指向新元素的 iterator } // 1) 范围 for使用 begin()/end() int sum 0; for (auto x : cv) sum x; // 2) 随机访问迭代器算术 下标访问 auto it cv.begin() 10; // 指向第 11 个元素值为 11 std::cout *it it[5] \n; // it[5] 即第 16 个元素值为 16 std::cout *std::next(cv.begin(), 3) \n; // 3) 反向迭代rbegin()/rend() for (auto rit cv.rbegin(); rit ! cv.rend(); rit) { // 逆序处理*rit 依次为 100, 99, ..., 1 } // 4) const 容器cbegin()/cend()/crbegin()/crend() const tbb::concurrent_vectorint ccv cv; auto cit ccv.cbegin(); // const_iterator auto crit ccv.crbegin(); // const_reverse_iterator auto crit2 ccv.crend() - 1; // 反向随机访问同样可用 std::cout *cit *crit *crit2 \n; // 5) 与其他容器的迭代器互操作const_iterator 与 iterator 可混合比较 std::vectorint ref(100); std::iota(ref.begin(), ref.end(), 1); bool same std::equal(ref.begin(), ref.end(), ccv.cbegin()); std::cout std::boolalpha same \n; return 0; }编译时需包含 oneTBB 头文件并链接 TBB 库。示例中用到的两个细节均来自源码事实其一push_back的返回类型是iteratorconcurrent_vector.h L408–L414 及 L775–L789 的internal_emplace_back你可以拿到指向新元素的迭代器其二第 5 步里std::equal一端是std::vectorint::iterator另一端是concurrent_vector::const_iterator能比较成功的前提是标准库算法通过std::equal的内部实现分别解引用两端而concurrent_vector自身内部的iterator/const_iterator混合比较则由上面 L195–L223 的友元模板直接支持。与并行迭代 range() 的关系同一规格目录下的 parallel_iteration.rst 规定了range_type/const_range_type容器提供range(grainsize)成员返回满足ContainerRange要求的区间对象可直接喂给parallel_for、parallel_for_each、parallel_reduce等并行算法。从源码看该区间正是由本篇讨论的迭代器构建的L437–L444// Get range for iterating with parallel algorithms range_type range( size_t grainsize 1 ) { return range_type(begin(), end(), grainsize); }其中range_type继承自tbb::blocked_rangeiterator而blocked_range要求两端点可作差、可取中点——这恰好再次印证了vector_iterator满足RandomAccessIterator这一前提正因为迭代器支持 O(1) 的差值与中点计算段表才能被高效地切分成多个并行块。可以说随机访问迭代器既是用户层面的便利也是 TBB 并行算法在该容器上工作的前提。测试用例中的迭代器行为验证仓库自带的测试对反向迭代器族做了直接验证test_concurrent_vector.cpp L296–L299const vector_t cvcr c; REQUIRE( utils::IsEqual()(cvcr.front(), *(c2.rend()-1)) ); REQUIRE( utils::IsEqual()(cvcr.back(), *c2.rbegin())); REQUIRE( utils::IsEqual()(*c.cbegin(), *(c.crend()-1)) ); REQUIRE( utils::IsEqual()(*(c.cend()-1), *c.crbegin()) );这组断言同时验证了三件事front()/back()与反向迭代器的首尾位置一致rbegin()/crend()、rend()/cbegin()的相对关系符合标准定义以及反向迭代器的随机访问算术rend()-1、crend()-1工作正常。小结concurrent_vector的iterator/const_iterator满足 ISO C 标准的RandomAccessIterator要求四组接口begin/cbegin、end/cend、rbegin/crbegin、rend/crend语义与std::vector一致迭代器由“容器指针 逻辑下标 元素指针缓存”三个成员构成全部算术与比较只依赖下标均为 O(1)容器底层是指数增长的段表迭代器在下标为 2 的幂时判定跨段并作废缓存指针从而在“物理上分段”的存储上维持连续遍历的高效率由于end()是调用时刻的 size 快照多线程场景下建议先固定区间边界再迭代该迭代器同时是range()并行区间的基础直接支撑parallel_for等 TBB 并行算法在concurrent_vector上的使用。【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考