资讯详情

oneTBB concurrent_set 构造、析构与拷贝赋值完全指南:生命周期管理与底层跳表实现

📅 2026/10/9 5:20:14 | 华诺云谱 👁 阅读
oneTBB concurrent_set 构造、析构与拷贝赋值完全指南:生命周期管理与底层跳表实现
并发编程高性能计算【免费下载链接】oneTBBoneAPI Threading Building Blocks (oneTBB)项目地址https://gitcode.com/gh_mirrors/on/oneTBB点击查看免费下载导读本文基于 oneAPI Threading Building BlocksoneTBB官方参考文档 construction_destruction_copying.rst系统讲解oneapi::tbb::concurrent_set的构造、析构、拷贝/移动与赋值语义。concurrent_set是 oneTBB 提供的并发有序关联容器适用于多线程环境下对唯一键集合进行安全读写。读完本文你将掌握其全部构造函数、赋值运算符的精确语义、分配器传播规则POCMA/POCPA以及这些接口在底层跳表skip list实现中的实际行为并能正确规避与容器并发操作导致未定义行为的常见陷阱。一、concurrent_set 是什么从类声明看类型骨架在深入生命周期接口之前先看它的类型定义。concurrent_set位于 include/oneapi/tbb/concurrent_set.h在头文件末尾通过using detail::d3::concurrent_set;注入到oneapi::tbb命名空间template typename Key, typename Compare std::lessKey, typename Allocator tbb::tbb_allocatorKey class concurrent_set : public concurrent_skip_list set_traitsKey, Compare, geometric_level_generator32, Allocator, false { // ... };由此可以确认三个关键事实模板参数与默认值Key是元素类型Compare默认是std::lessKey即升序排序Allocator默认是tbb::tbb_allocatorKey这是 oneTBB 的可扩展内存分配器见 include/oneapi/tbb/tbb_allocator.h能显著降低多线程内存分配竞争。底层结构是跳表concurrent_set继承自concurrent_skip_list其set_traits中allow_multimapping false说明每个键唯一键的层级由geometric_level_generator32按几何分布随机生成最大层级 32实现见 include/oneapi/tbb/detail/_concurrent_skip_list.h 末尾的geometric_level_generator类。value_type 即 key_typeset_traits::value_type key_type与标准库std::set一致容器存储的就是键本身。concurrent_set与标准库std::set的对应关系可总结如下特性std::setconcurrent_set底层结构红黑树并发跳表默认比较器std::lessKeystd::lessKey默认分配器std::allocatorKeytbb::tbb_allocatorKey唯一键约束是是allow_multimapping false并发安全否是多线程安全读写二、空容器构造函数原文档给出了三种创建空concurrent_set的构造函数concurrent_set(); explicit concurrent_set( const key_compare comp, const allocator_type alloc allocator_type() ); explicit concurrent_set( const allocator_type alloc );语义说明无参构造创建一个空容器使用默认比较器std::lessKey和默认分配器tbb::tbb_allocatorKey。带比较器构造使用函数对象comp进行所有key_type之间的比较影响排序顺序与键相等判定用alloc分配内存。仅分配器构造explicit关键字禁止隐式转换必须显式传参此构造内部等价于concurrent_set(key_compare(), alloc)。这些构造函数在底层concurrent_skip_list中均有对应实现include/oneapi/tbb/detail/_concurrent_skip_list.h 第 321-330 行无参构造将头指针my_head_ptr置空、最大高度my_max_height置 0此时容器是懒初始化的——头节点直到首次插入元素时才通过create_head_if_necessary()分配见_concurrent_skip_list.h第 1200-1216 行使用compare_exchange_strong保证多线程并发创建头节点时只有一个线程成功其余线程释放自己新建的节点。带比较器构造则额外初始化my_node_allocator(alloc)和my_compare(comp)两个成员。三、从元素序列构造迭代器区间与 initializer_list3.1 迭代器区间构造template typename InputIterator concurrent_set( InputIterator first, InputIterator last, const key_compare comp key_compare(), const allocator_type alloc allocator_type() ); template typename InputIterator concurrent_set( InputIterator first, InputIterator last, const allocator_type alloc allocator_type() );构造的容器包含半开区间[first, last)中的所有元素按比较器comp排序。需要特别注意的是如果区间内存在多个相等的键最终插入哪个元素是未指定的——这是集合类容器的标准行为因为concurrent_set保证键唯一性但不保证重复键的谁先谁后。RequirementsInputIterator必须满足 ISO C 标准[input.iterators]一节对 InputIterator 的要求即支持*it、it、it ! end等基本遍历操作。底层实现_concurrent_skip_list.h第 332-342 行先按传入的比较器与分配器初始化空容器再调用internal_copy(first, last)逐元素插入。internal_copy第 1141-1151 行使用 oneTBB 的try_call异常保护机制若插入过程中抛出异常会回滚清空所有已插入元素并释放头节点保证构造失败时容器不会泄漏资源。3.2 initializer_list 构造concurrent_set( std::initializer_listvalue_type init, const key_compare comp key_compare(), const allocator_type alloc allocator_type() ); concurrent_set( std::initializer_listvalue_type init, const allocator_type alloc );这两个构造函数分别等价于concurrent_set(init.begin(), init.end(), comp, alloc)和concurrent_set(init.begin(), init.end(), alloc)即完全委托给迭代器区间版本。底层实现见_concurrent_skip_list.h第 344-349 行。实际使用示例#include oneapi/tbb/concurrent_set.h #include vector using oneapi::tbb::concurrent_set; int main() { std::vectorint v {5, 1, 4, 2, 3}; // 从迭代器区间构造 concurrent_setint s1(v.begin(), v.end()); // 从 initializer_list 构造 concurrent_setint s2{5, 1, 4, 2, 3, 2, 2}; // 重复的 2 只保留一个 // 自定义比较器降序与显式分配器 concurrent_setint, std::greaterint s3(v.begin(), v.end()); return 0; }四、拷贝构造复制内容与分配器选择concurrent_set( const concurrent_set other ); concurrent_set( const concurrent_set other, const allocator_type alloc );拷贝构造构造other的一个副本。若未显式提供分配器参数则通过以下调用获得新容器的分配器std::allocator_traitsallocator_type::select_on_container_copy_construction(other.get_allocator())这遵循 C 标准库惯例默认情况下select_on_container_copy_construction返回原分配器的副本因此拷贝构造通常不传播分配器。未定义行为警告如果在调用拷贝构造的同时有其他线程正在对other进行并发操作插入、删除、遍历等行为是未定义的。原因很明确——concurrent_set的并发安全保证仅针对同一个容器实例被多个线程同时安全读写而拷贝构造本身是一次非原子的整体快照操作它需要稳定地遍历other的全部元素若此时other被并发修改快照的一致性无法保证。底层实现_concurrent_skip_list.h第 351-368 行新容器复用other的比较器my_compare头指针置空后通过internal_copy(other)遍历插入全部元素并带__TBB_ASSERT(size() other.size())断言校验副本大小一致。五、移动构造转移底层跳表所有权concurrent_set( concurrent_set other ); concurrent_set( concurrent_set other, const allocator_type alloc );移动构造使用移动语义用other的内容构造新容器。移动后other处于合法但未指定的状态valid but unspecified即other仍可被安全析构或重新赋值使用但其元素内容、大小均不确定。未显式提供分配器时新分配器通过std::move(other.get_allocator())获得移动原分配器状态。同样若移动期间有其他线程并发操作other行为未定义。底层实现值得细看_concurrent_skip_list.h第 370-385 行不带分配器的移动构造调用internal_move第 786-798 行直接原子地转移头指针my_head_ptr.store(other.my_head_ptr.load(...))然后把other的头指针置空、my_max_height置 0。这是常量时间 O(1)的操作因为跳表节点本身没有移动只是所有权转移。带分配器的移动构造则根据分配器特性分派若std::allocator_traitsAllocator::is_always_equal为true即分配器无状态、始终相等直接走internal_move转移所有权否则比较两个分配器——相等则转移所有权不相等则退化为逐元素移动构造internal_copy(std::make_move_iterator(...))第 800-816 行因为节点必须由原容器自己的分配器释放跨分配器共享节点是非法的。六、析构函数回收全部存储~concurrent_set();析构函数销毁容器调用所有存储元素的析构函数并释放使用的存储空间。底层concurrent_skip_list::~concurrent_skip_list()_concurrent_skip_list.h第 387-390 行依次执行clear()遍历并销毁所有值节点对每个节点先调用元素析构函数node_allocator_traits::destroy再释放节点内存delete_value_node第 1190-1194 行delete_head()释放哨兵头节点并置空头指针。析构期间若存在并发操作*this例如其他线程仍在插入或遍历同样是未定义行为。实际工程中务必保证在容器析构前所有使用该容器的线程都已结束并完成同步如join或barrier。七、赋值运算符拷贝、移动与 initializer_list7.1 拷贝赋值concurrent_set operator( const concurrent_set other );用other中元素的副本替换*this中的全部元素。底层实现_concurrent_skip_list.h第 392-400 行做了三件事若this ! other自赋值检查先clear()清空当前内容调用copy_assign_allocators——仅当std::allocator_traitsallocator_type::propagate_on_container_copy_assignment::valuePOCMA 的拷贝赋值版本即 POCA为true时才拷贝赋值分配器否则保留当前分配器这符合标准库规则非传播分配器赋值时目标容器继续用原分配器管理已有元素复制other的比较器再internal_copy(other)逐元素插入。返回*this的引用。7.2 移动赋值concurrent_set operator( concurrent_set other );用移动语义以other的元素替换*this的元素移动后other合法但未指定。底层实现第 402-414 行先clear()并delete_head()释放旧存储再依据两个条件分派internal_move_assign第 1222-1232 行若propagate_on_container_move_assignment::valuePOCMA为true或分配器is_always_equal为true直接internal_move转移整个跳表所有权O(1)否则比较两个分配器相等则转移所有权不相等则逐元素移动构造std::make_move_iterator因为目标容器无法用other的分配器释放节点。返回*this的引用。7.3 initializer_list 赋值concurrent_set operator( std::initializer_listvalue_type init );用init中的元素替换*this的全部元素。与序列构造同理若init包含多个相等键插入哪个元素未指定。底层实现_concurrent_skip_list.h第 416-421 行以及concurrent_set.h第 90-93 行的重写版本先clear()再insert(init.begin(), init.end())最终返回*this引用。八、与 CTAD 推导指引的关系构造与赋值接口和 C17 类模板实参推导CTAD紧密相关。concurrent_set除拷贝/移动构造自带隐式推导指引外还在 include/oneapi/tbb/concurrent_set.h 第 116-147 行显式提供了四个推导指引在__TBB_CPP17_DEDUCTION_GUIDES_PRESENT宏保护下官方文档详见 deduction_guides.rst迭代器区间 可选比较器/分配器 →concurrent_setiterator_value_tIt, Compare, Allocator迭代器区间 分配器 →concurrent_setiterator_value_tIt, std::less..., Allocatorinitializer_list 可选比较器/分配器 →concurrent_setKey, Compare, Allocatorinitializer_list 分配器 →concurrent_setKey, std::lessKey, Allocator其中iterator_value_t定义为typename std::iterator_traitsInputIterator::value_type。推导指引参与重载解析还需满足三个约束InputIterator满足[input.iterators]、Allocator满足[allocator.requirements]、Compare不满足分配器要求避免二义性。这使得构造时模板实参可以完全省略#include oneapi/tbb/concurrent_set.h #include vector int main() { std::vectorint v; // 推导为 concurrent_setint oneapi::tbb::concurrent_set cs1(v.begin(), v.end()); // 推导为 concurrent_setint oneapi::tbb::concurrent_set cs2({1, 2, 3}); }九、多线程下的正确使用约束务必牢记综合全文各构造/赋值接口concurrent_set的生命周期操作遵循一套统一的并发约束规则操作并发安全前提说明构造任何形式源区间/other无并发修改迭代器区间构造要求[first, last)来源容器不被并发修改拷贝/移动构造要求other无并发操作析构*this无并发操作析构前所有引用该容器的线程必须已结束拷贝赋值*this与other均无并发操作需要清空 重建两个容器都必须是静止的移动赋值*this与other均无并发操作同上插入/查找/遍历运行时多线程可安全并发这是concurrent_set设计的核心并发能力换句话说构造/析构/赋值这些整体性操作必须在容器静止时进行而单元素的insert、find、erase、迭代等才是设计为可跨线程并发调用的操作。这一区别是使用 oneTBB 并发容器最常见的误区请务必在代码评审中关注。十、测试佐证与进一步阅读oneTBB 的测试套件覆盖了上述生命周期接口test/common/concurrent_ordered_common.h 中TestSet系列模板测试覆盖了从std::initializer_list构造、默认/非默认比较器与分配器组合等场景第 228-238 行附近test/conformance/conformance_concurrent_set.cpp 对concurrent_set的并发语义做一致性测试。如果你想继续深入推荐按以下顺序阅读include/oneapi/tbb/concurrent_set.hconcurrent_set完整类定义、推导指引与merge接口include/oneapi/tbb/detail/_concurrent_skip_list.h跳表底层实现构造函数、internal_copy/internal_move、分配器传播与节点管理参考手册中concurrent_set的其余章节iterators.rst、lookup.rst、safe_modifiers.rst、unsafe_modifiers.rst、size_and_capacity.rst 与 observers.rst。掌握好concurrent_set的构造、析构与赋值语义是正确使用 oneTBB 并发容器的基础——它不仅决定了代码能否编译通过更直接决定了多线程程序在容器生命周期临界点上是否安全。赞分享并发编程高性能计算【免费下载链接】oneTBBoneAPI Threading Building Blocks (oneTBB)项目地址https://gitcode.com/gh_mirrors/on/oneTBB点击查看免费下载相关推荐oneTBB concurrent_priority_queue 对象生命周期全解析构造、析构、拷贝与赋值语义oneTBB concurrent_priority_queue 对象生命周期全解析构造、析构、拷贝与赋值语义 本文以 oneAPI Threading Bu开发工具构建工具系统编程oneTBB concurrent_map 全生命周期指南构造、析构与拷贝/移动语义详解oneTBB concurrent_map 全生命周期指南构造、析构与拷贝/移动语义详解 导读 本文围绕 oneAPI Threading Building并发编程高性能计算oneTBB concurrent_queue 构造、析构与拷贝语义详解生命周期管理的并发安全边界oneTBB concurrent_queue 构造、析构与拷贝语义详解生命周期管理的并发安全边界 oneapi::tbb::concurrent_queue并发编程高性能计算上一篇DLSS Swapper 教程3 步替换游戏里的 DLSS 版本随时一键回退下一篇ImmortalWrt固件升级失败路由器变砖三条救援路线30分钟救活创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑