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),仅供参考

相关新闻

JavaScript / TypeScript 易踩坑知识点 —— 性能与内存类

JavaScript / TypeScript 易踩坑知识点 —— 性能与内存类

JavaScript / TypeScript 易踩坑知识点 —— 性能与内存类 一、循环中频繁操作 DOM 问题说明 在循环中每次操作 DOM 都会触发浏览器的重排(Reflow)和重绘(Repaint),性能极差。 踩坑示例 // ❌ 每次循环都操作 DOM&…

2026/10/9 5:20:26 阅读更多 →
安装Pyside 6

安装Pyside 6

安装Pyside 6一、前言最近的AI Agent编写需要用到界面交互,比如CLI对话机器人,我们只能看到命令行,没法看到交互界面。想到了使用Python写界面,就想到了PyQt和PySide,PySide还没有用过(以前都是用PyQt&…

2026/10/9 5:20:26 阅读更多 →
axure9.0 如何打造一个计时器(带前导零)

axure9.0 如何打造一个计时器(带前导零)

原理:在 Axure 9.0 中制作计时器,核心思路是利用“页面载入时”事件形成一个循环。🕹️ 第一步:创建变量与界面创建全局变量:点击顶部菜单「项目」→「全局变量」。在弹出的窗口中,点击“添加”&#xff0c…

2026/10/9 5:20:26 阅读更多 →

最新新闻

基于伴随灵敏度分析与肿瘤生长模型的放疗计划优化方法

基于伴随灵敏度分析与肿瘤生长模型的放疗计划优化方法

在肿瘤放疗计划里,我们通常习惯把靶区勾画好、给一个最大耐受的处方剂量,然后按均匀分布的射束照下去。但肿瘤在治疗周期里是不断变化的,细胞增殖速度快慢、对剂量的响应强度,每一天都不一样。如果能让放射治疗计划跟着肿瘤的“实…

2026/10/9 6:25:18 阅读更多 →
单细胞衰老时钟全流程解析:从数据质控到模型训练

单细胞衰老时钟全流程解析:从数据质控到模型训练

单细胞测序技术火了这么多年,数据越攒越多,但绝大多数分析还是停留在“聚类看分群、注释找细胞类型”的层面。直到“衰老时钟”这个概念从表观遗传学延展到单细胞转录组,很多人才意识到,我们完全可以回答一个更刁钻的问题&#xf…

2026/10/9 6:25:18 阅读更多 →
灰狼优化算法GWO从原理到Python实现:参数分析与工程踩坑经验

灰狼优化算法GWO从原理到Python实现:参数分析与工程踩坑经验

灰狼优化算法(GWO算法)这几年的热度,在元启发式算法里算是现象级的。写论文的拿它做对比算法,做工程的拿它做参数寻优,搞机器学习的拿它调超参数,甚至连不少教材都把GWO列进了智能优化算法的必修清单。它能…

2026/10/9 6:25:18 阅读更多 →
OpenRig 实战指南:基于 Node.js + tmux 的本地 AI 工具链搭建

OpenRig 实战指南:基于 Node.js + tmux 的本地 AI 工具链搭建

1. OpenRig 是什么:一个被误读的开源项目名与真实技术现场 OpenRig 这个词在当前中文技术社区里,正经历一场典型的“语义漂移”——它既不是某个广为人知的成熟开源项目,也不是官方发布的工具套件,而更像是一个在开发者私有工作流…

2026/10/9 6:25:17 阅读更多 →
基于SpringBoot+Vue+MyBatis的汽车租赁管理系统设计与实现

基于SpringBoot+Vue+MyBatis的汽车租赁管理系统设计与实现

坦白说,看到标题里“管理系统管理系统”这个重复词的时候,我愣了一下。但做技术这行久了也明白,这类源码发布贴经常是复制标题时手滑。真正让我感兴趣的是后面那串技术组合:SpringBoot Vue MyBatis MySQL。这四个词放在一起&am…

2026/10/9 6:25:17 阅读更多 →
JS对象与BOM协作指南:从类型判断到页面跳转高频实战

JS对象与BOM协作指南:从类型判断到页面跳转高频实战

1. 从 "JS 对象撞上 BOM" 聊起:一次重构翻车现场前段时间我重构一个老项目里的页面跳转逻辑,原本只是想把散落在按钮回调里的 "window.location.href xxx" 抽成一个统一的导航配置对象。改完以后测试同学跑了一圈,反馈说…

2026/10/9 6:24:16 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 10:10:36 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 6:17:20 阅读更多 →