mold 仓库 vendored TBB 解析:concurrent_bounded_queue 非成员 swap 的签名、语义与源码实现
【免费下载链接】moldmold: A Modern Linker 🦠项目地址: https://gitcode.com/GitHub_Trending/mo/mold
本文聚焦 oneAPI Threading Building Blocks(TBB)中concurrent_bounded_queue容器提供的非成员swap函数,以 mold 仓库所 vendored 的 TBB 源码与规范文档为事实依据,讲解其函数签名、与成员swap的等价关系、底层实现机制及正确使用边界。读者读完本文后,将能在自己的并发代码中正确使用swap交换两个有界并发队列,并理解该操作为何被归类为"非并发安全"操作、其内部到底交换了什么。
一、规范原文:非成员 swap 的定义
在 mold 仓库所携带的 TBB 规范文档中,非成员swap是concurrent_bounded_queue类簇(class cluster)文档的一个独立小节,原文位于 non_member_swap.rst,其完整定义如下:
template <typename T, typename Allocator> void swap( concurrent_bounded_queue<T, Allocator>& lhs, concurrent_bounded_queue<T, Allocator>& rhs );规范对它的语义描述只有一句话:Equivalent tolhs.swap(rhs),即"等价于调用lhs.swap(rhs)"。
这看似简单的一句话,实际上携带了三条重要信息:
- 这是一个函数模板,模板参数
T与Allocator与concurrent_bounded_queue<T, Allocator>的两个模板形参一一对应,因此它只能用于交换同一元素类型、同一分配器类型的两个队列; - 它不返回任何值(返回类型为
void),交换动作完成后,lhs持有原rhs的全部元素与容量状态,rhs持有原lhs的全部元素与容量状态; - 它的行为完全委托给成员函数
lhs.swap(rhs),因此理解非成员swap的关键,在于理解成员swap的语义——这正是源码实现可以验证的地方。
二、成员 swap:语义与约束
规范文档 unsafe_member_functions.rst 中,成员swap被归入"not concurrently safe"(非并发安全)成员函数一节,定义如下:
void swap( concurrent_bounded_queue& other );其语义为:
- Swaps contents of
*thisandother:交换两个队列的全部内容(包括已入队元素、容量capacity()等内部状态); - 分配器(allocator)处理遵循标准容器惯例:当
std::allocator_traits<allocator_type>::propagate_on_container_swap::value为true时,交换两个队列的分配器; - 否则(即分配器不随容器传播时),若
get_allocator() != other.get_allocator(),则行为未定义(undefined behavior)。
这一约束意味着:在使用默认分配器(tbb::cache_aligned_allocator<T>,由 concurrent_queue.h 中template <typename T, typename Allocator = tbb::cache_aligned_allocator<T>> class concurrent_bounded_queue给出)或确保两个队列使用相同分配器的前提下,swap是安全且廉价的;反之,若两个队列由不同分配器构造且分配器类型不传播,则必须避免调用swap。
三、源码级实现剖析:swap 到底交换了什么
在 mold 仓库 vendored 的 TBB 头文件 concurrent_queue.h 中,可以完整还原从非成员swap到内部状态交换的调用链。
3.1 非成员 swap:友元函数转发
在类定义内部,TBB 将非成员swap声明为友元函数(friend),并将调用转发给成员swap(concurrent_queue.h#L669-L671):
friend void swap( concurrent_bounded_queue& lhs, concurrent_bounded_queue& rhs ) { lhs.swap(rhs); }这与规范文档"Equivalent tolhs.swap(rhs)"的描述完全一致——非成员版本只是一个薄薄的转发层。作为对比,concurrent_queue(无界版本)也采用了完全相同的 friend 转发模式(concurrent_queue.h#L270-L272)。
3.2 成员 swap:交换三个内部状态字段
成员swap的实现核心是私有辅助函数internal_swap(concurrent_queue.h#L552-L557):
void internal_swap( concurrent_bounded_queue& src ) { using std::swap; swap(my_queue_representation, src.my_queue_representation); swap(my_capacity, src.my_capacity); swap(my_monitors, src.my_monitors); }它通过std::swap依次交换三个数据成员:
| 内部成员 | 类型 | 交换后影响 |
|---|---|---|
my_queue_representation | concurrent_queue_rep<T, Allocator>* | 底层环形队列(ticket-based ring buffer)的指针,交换后两个队列的元素集合随之交换 |
my_capacity | std::ptrdiff_t | 有界容量,交换后各自继承对方的容量上限 |
my_monitors | r1::concurrent_monitor* | 用于阻塞语义的条件监视器集合,交换后阻塞/唤醒逻辑作用在对方的监视器上 |
从源码结构可以推断,这是一个常量级时间(O(1))的指针/标量交换:它既不搬移元素,也不释放或重新分配存储,因此即使队列中包含大量元素,swap的开销也与队列规模无关。这正是以std::swap为基础的指针交换的典型特征。
注意:my_abort_counter(std::atomic<unsigned>)并未参与交换,从源码结构看,abort()相关的中止状态保持在原对象上,这与internal_swap只交换容器内容与容量状态的语义是一致的。
3.3 有界队列的整体结构
从类定义(concurrent_queue.h#L320-L321 及 L662-L667)可以看出,concurrent_bounded_queue的完整内部状态为:
queue_allocator_type my_allocator; // 分配器 queue_representation_type* my_queue_representation; // 环形队列表示 std::ptrdiff_t my_capacity; // 容量上限 std::atomic<unsigned> my_abort_counter; // 中止计数器 r1::concurrent_monitor* my_monitors; // 阻塞监视器结合internal_swap的实现,swap的语义边界就非常清晰:它交换"队列内容 + 容量 + 阻塞监视器",而不交换分配器与中止计数器。分配器是否交换,取决于propagate_on_container_swap,这在规范层面由成员swap的约束条款(见第二节)管辖。
四、正确使用方式与示例
4.1 基本用法
由于非成员swap由类内 friend 定义提供,它通过实参依赖查找(ADL, Argument-Dependent Lookup)在oneapi::tbb命名空间内被找到,使用时无需显式using声明:
#include <oneapi/tbb/concurrent_bounded_queue.h> #include <string> void swap_example() { oneapi::tbb::concurrent_bounded_queue<std::string> qa, qb; qa.set_capacity(100); qb.set_capacity(8); qa.push("hello"); qb.push("world"); qb.push("!"); // 非成员 swap:等价于 qa.swap(qb) swap(qa, qb); // 交换后:qa 容量为 8,含 2 个元素;qb 容量为 100,含 1 个元素 // qa.size() == 2, qa.capacity() == 8 // qb.size() == 1, qb.capacity() == 100 }由于模板参数要求两个队列的T与Allocator完全一致,交换"不同元素类型"或"不同分配器类型"的队列在编译期即被拒绝,这也是标准库std::swap式容器交换的一贯约束。
4.2 典型应用场景
concurrent_bounded_queue在规范中支持多线程并发push/pop/emplace(见 safe_member_functions.rst),而swap属于非并发安全操作,其典型应用场景包括:
- 单线程初始化阶段的整体替换:在队列尚未暴露给工作线程之前,用预构建好的队列原子性地替换当前队列(尽管 swap 本身不是原子的,但在没有并发访问的前提下是安全的);
- 容量策略切换:借助 swap 让一个已填满的队列与一个容量更大的空队列互换,从而"扩容"——因为
my_capacity参与交换,这一操作比逐元素搬移高效得多; - 一次性清空快照:将队列与一个空队列交换,从而在不逐元素
pop的情况下拿走全部内容。
4.3 注意事项与边界
- 并发安全边界:
swap与clear、迭代器访问等一样属于"非并发安全"成员函数(见 unsafe_member_functions.rst),不得与其它线程对同一队列的push/pop等并发安全操作同时进行,否则属于数据竞争; - 分配器约束:当
propagate_on_container_swap为false且两个队列分配器不相等时,swap行为未定义,应避免使用; - 与无界队列的关系:本规范文档属于
concurrent_bounded_queue类簇,无界concurrent_queue同样提供了语义一致的非成员swap友元实现(concurrent_queue.h#L270-L272),两者使用方式一致。
五、小结
concurrent_bounded_queue的非成员swap虽然只有一个函数签名和一句"等价于lhs.swap(rhs)"的规范描述,但其背后是一个完整的、可被源码验证的实现链:非成员 friend 函数 → 成员swap→ 私有internal_swap→ 三个内部状态成员(环形队列指针、容量、监视器)的 O(1) 交换。理解这条链路,就能准确回答"swap 交换了什么、开销多大、何时安全"这三个实践中最关键的问题。本主题对应的规范原文位于 non_member_swap.rst,配套的成员函数语义见 unsafe_member_functions.rst 与 safe_member_functions.rst,完整实现位于 concurrent_queue.h。
【免费下载链接】moldmold: A Modern Linker 🦠项目地址: https://gitcode.com/GitHub_Trending/mo/mold
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考