TBB concurrent_vector 迭代器实现详解:RandomAccessIterator 语义、源码机制与实战用法
【免费下载链接】moldmold: A Modern Linker 🦠项目地址: https://gitcode.com/GitHub_Trending/mo/mold
本文以 oneTBB(oneAPI Threading Building Blocks)规格文档中concurrent_vector容器的迭代器规范为主体,完整梳理begin/end/rbegin/rend等接口族的语义与签名,并结合 oneTBB 源码逐层拆解迭代器的三成员结构、指针缓存与段(segment)边界失效机制,帮助读者既会正确使用这些迭代器,也能理解随机访问迭代器在一个可并发增长的容器上是如何被高效且安全地实现的。
规范出处与迭代器的核心承诺
迭代器规范的原始文档位于 iterators.rst,它与 concurrent_growth.rst、parallel_iteration.rst 等同目录文档共同构成concurrent_vector的完整规格。
该文档给出的第一条也是最重要的承诺是:
The types
concurrent_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 中迭代器类别被显式定义为随机访问标签(L51):
using 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 与 cbegin
iterator begin(); const_iterator begin() const; const_iterator cbegin() const;返回值:指向向量中第一个元素的迭代器。
end 与 cend
iterator end(); const_iterator end() const; const_iterator cend() const;返回值:指向向量中最后一个元素之后位置的迭代器(即惯用的“尾后迭代器”)。
rbegin 与 crbegin
reverse_iterator rbegin(); const_reverse_iterator rbegin() const; const_reverse_iterator crbegin() const;返回值:反向迭代器,指向逆序向量中的第一个元素(即原向量的最后一个元素)。
rend 与 crend
reverse_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()是下标 0,end()是下标size(); - 反向迭代器直接用标准库适配器
std::reverse_iterator包装正向迭代器得到,类型定义在 L274–L277:
using iterator = vector_iterator<concurrent_vector, value_type>; using const_iterator = vector_iterator<concurrent_vector, const value_type>; using reverse_iterator = std::reverse_iterator<iterator>; using const_reverse_iterator = std::reverse_iterator<const_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–L490):
size_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–L178):
private: // 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_vector;my_index:元素在容器中的逻辑下标,是迭代器身份的核心——所有比较和算术都只依赖它;my_item:一个可选的元素指针缓存。为nullptr时回退到通过internal_subscript(my_index)按段表查表取址;非空时直接解引用,省去一次查表。
解引用操作符operator*展示了缓存的验证逻辑(L111–L119):
reference 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–L736):
static 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–L139):
vector_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_iterator<Vector, T>::difference_type operator-( const vector_iterator<Vector, T>& i, const vector_iterator<Vector, U>& j ) { using difference_type = typename vector_iterator<Vector, T>::difference_type; return static_cast<difference_type>(i.my_index) - static_cast<difference_type>(j.my_index); } template <typename Vector, typename T, typename U> bool operator==( const vector_iterator<Vector, T>& i, const vector_iterator<Vector, 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_vector<int> 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_vector<int>& 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::vector<int> 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的返回类型是iterator(concurrent_vector.h L408–L414 及 L775–L789 的internal_emplace_back),你可以拿到指向新元素的迭代器;其二,第 5 步里std::equal一端是std::vector<int>::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_range<iterator>,而blocked_range要求两端点可作差、可取中点——这恰好再次印证了vector_iterator满足RandomAccessIterator这一前提:正因为迭代器支持 O(1) 的差值与中点计算,段表才能被高效地切分成多个并行块。可以说,随机访问迭代器既是用户层面的便利,也是 TBB 并行算法在该容器上工作的前提。
测试用例中的迭代器行为验证
仓库自带的测试对反向迭代器族做了直接验证,test_concurrent_vector.cpp L296–L299:
const 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),仅供参考