oneapi::tbb::concurrent_multiset 查找操作完全指南:count、find、contains、lower_bound、upper_bound 与 equal_range
oneapi::tbb::concurrent_multiset 查找操作完全指南count、find、contains、lower_bound、upper_bound 与 equal_range【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/moldoneapi::tbb::concurrent_multiset是 oneAPI Threading Building BlocksoneTBB提供的有序并发容器允许存储多个等价元素并支持并发的插入、查找与遍历。本文以 oneTBB 官方规范中 lookup.rst 为骨架逐一剖析六大查找成员函数的签名、语义、返回规则与透明查找heterogeneous lookup约束并结合仓库中 concurrent_set.h 与_concurrent_skip_list.h的跳表实现源码说明每个 API 底层是如何工作的。读完本文你将能够在多线程场景下正确、高效地使用这些查找接口并理解其在跳表结构上的查找路径。查找操作的整体并发语义lookup.rst 文档开头给出了所有查找方法的统一前提All methods in this section can be executed concurrently with each other, concurrently-safe modifiers and while traversing the container.这句话包含三层含义是整个查找 API 设计的核心约束查找与查找之间可并发多个线程可以同时调用count、find、contains、lower_bound、upper_bound、equal_range任意组合都安全查找与并发安全修改操作可并发即查找可以与 safe_modifiers.rst 中定义的insert、emplace、merge等操作同时进行查找与遍历可并发在遍历容器见 iterators.rst的同时执行查找是安全的。需要特别注意的是concurrent_multiset不支持并发的删除操作。所有擦除类操作unsafe_erase、unsafe_extract、clear都带unsafe_前缀属于 unsafe_modifiers.rst它们与查找并发执行的结果未定义。因此查找 API 的并发保证严格限定在查找 × 查找、查找 × 安全修改与查找 × 遍历这三个组合上。关于等价equivalent的判定需要回到类模板的定义。从 concurrent_multiset_cls.rst 的类摘要可见template typename T, typename Compare std::lessT, typename Allocator tbb_allocatorT class concurrent_multiset;容器使用Compare默认std::lessT判定元素顺序两个元素a与b等价当且仅当!comp(a, b) !comp(b, a)。由于concurrent_multiset允许存储多个等价元素即多重映射源码中set_traits的allow_multimapping true因此下面所有查找接口的语义都围绕等价于 key 的元素集合展开。count统计等价元素个数count 有两个重载用于返回与key等价的元素个数size_type count( const key_type key ); template typename K size_type count( const K key );第一个版本接收容器的key_type对 multiset 而言key_type value_type T第二个是透明查找重载只有当key_compare::is_transparent为有效类型时才参与重载决议详见下文透明查找一节。count 的语义非常直接返回容器中等价于key的元素数量。对于concurrent_multiset而言由于允许多个等价元素共存返回值可以大于 1这与concurrent_set不同——对唯一的concurrent_set来说count 的结果恒为 0 或 1。从源码看_concurrent_skip_list.h中的internal_count针对是否允许多重映射做了分支template typename K size_type internal_count( const K key ) const { if (allow_multimapping) { // TODO: reimplement without double traversal std::pairconst_iterator, const_iterator r equal_range(key); return std::distance(r.first, r.second); } return size_type(contains(key) ? 1 : 0); }也就是说concurrent_multiset::count的底层实现是通过equal_range求出区间后用std::distance统计区间长度。源码中注释也明确指出这里存在双重遍历先 lower_bound 再找 upper_bound的优化空间属于实现细节不影响接口语义。仓库测试 test_concurrent_set.cpp 中的test_cycles_absense正是对 multiset count 语义的回归验证4 个线程各自向同一个tbb::concurrent_multisetint mset插入相同的i随后断言mset.count(i) num_threads即 4。这印证了 count 会完整统计所有等价元素。find查找等价元素并返回迭代器find 提供四个重载分别覆盖常量/非常量容器与普通/透明查找iterator find( const key_type key ); const_iterator find( const key_type key ) const; template typename K iterator find( const K key ); template typename K const_iterator find( const K key ) const;返回规则返回指向等价于key的元素的迭代器若不存在任何等价元素返回end()。关键语义细节如果容器中存在多个与key等价的元素返回哪一个元素是未指明的unspecified。这是多重容器multiset与唯一容器set的重要差异——concurrent_set中最多一个等价元素因此结果唯一而在concurrent_multiset中调用者不应假设 find 一定返回最早插入的元素或区间内的第一个元素。从源码看internal_find会根据allow_multimapping分发template typename K node_ptr internal_find(const K key) const { return allow_multimapping ? internal_find_multi(key) : internal_find_unique(key); }对于 multiset 走的是internal_find_multi从跳表当前最高层向下逐层搜索一旦在某层找到满足found(curr, key)即node ! nullptr !my_compare(key, get_key(node))的节点就立即返回。由于是从高层开始查找找到的往往是在高层路径上最先遇到的等价节点而非严格意义上的第一个等价元素——这正是文档声明未指明返回哪一个的底层原因。contains判断是否存在等价元素bool contains( const key_type key ) const; template typename K bool contains( const K key ) const;返回规则容器中至少存在一个与key等价的元素时返回true否则返回false。contains 是 C20 之后标准关联容器也引入的便捷接口其优势在于语义清晰调用者只关心在不在而不关心个数或具体位置。在源码层面contains的实现 就是bool contains( const key_type key ) const { return find(key) ! end(); }即内部复用 find 并与end()比较没有额外的独立查找逻辑。因此它的成本与 find 相同属于跳表上的一次并发查找。lower_bound定位第一个不小于 key 的元素iterator lower_bound( const key_type key ); const_iterator lower_bound( const key_type key ) const; template typename K iterator lower_bound( const K key ); template typename K const_iterator lower_bound( const K key ) const;返回规则返回指向容器中第一个不小于not less thankey的元素的迭代器。即返回满足!(element key)的最小元素位置若所有元素都小于key则返回end()。lower_bound 的底层实现调用internal_get_bound并以容器自身的比较器my_compare作为查找比较器iterator lower_bound( const key_type key ) { return iterator(internal_get_bound(key, my_compare)); }internal_get_bound从表头节点出发从最高层向下逐层执行internal_find_position最终返回定位到的节点——这是一条标准的跳表从左向右、自上而下的下界搜索路径。upper_bound定位第一个大于 key 的元素iterator upper_bound( const key_type key ); const_iterator upper_bound( const key_type key ) const; template typename K iterator upper_bound( const K key ); template typename K const_iterator upper_bound( const K key ) const;返回规则返回指向容器中第一个大于greater thankey的元素的迭代器若不存在这样的元素返回end()。注意 lower_bound 与 upper_bound 的分界语义lower_bound 返回不小于即语义下的第一个upper_bound 返回严格大于即语义下的第一个。两者配合即可刻画与key等价的整个区间。upper_bound 与 lower_bound 的源码实现差异仅在于比较器upper_bound使用not_greater_compareiterator upper_bound( const key_type key ) { return iterator(internal_get_bound(key, not_greater_compare(my_compare))); }not_greater_compare对原始比较器做逻辑取反包装从而把找到第一个不小于 key 的元素转化为找到第一个大于 key 的元素复用同一条internal_get_bound搜索路径。equal_range一次调用获取完整等价区间std::pairiterator, iterator equal_range( const key_type key ); std::pairconst_iterator, const_iterator equal_range( const key_type key ) const; template typename K std::pairiterator, iterator equal_range( const K key ); template typename K std::pairconst_iterator, const_iterator equal_range( const K key ) const;返回规则这是 multiset 语义最丰富的接口若容器中存在至少一个与key等价的元素返回迭代器对{f, l}f指向第一个与key等价的元素l指向紧随最后一个等价元素之后的元素即 upper_bound 的位置若不存在任何等价元素返回{end(), end()}。因此[f, l)恰好是容器中等价于key的所有元素构成的连续区间可以配合std::distance(f, l)统计个数这也正是前面提到的internal_count的做法或直接遍历区间访问所有等价元素。源码中internal_equal_range的实现思路是先用lower_bound(key)取得下界lb检查lb处的节点是否命中key若命中且为唯一容器allow_multimapping false直接让第二个迭代器前进一步result.second即得区间若命中且为多重容器则从lb节点开始用not_greater_compare沿跳表高层跳跃前进直到找到第一个大于key的节点作为区间上界若未命中保持{lb, lb}语义上等价于{end(), end()}因为此时 lb 处的元素不等于 key。透明查找Heterogeneous Lookup模板重载的参与条件本文所述六个 API 中除count/find/contains/lower_bound/upper_bound/equal_range各自的key_type版本外每个方法都还有一个template typename K版本。lookup.rst 对所有这些模板重载都给出了相同的参与条件This overload only participates in overload resolution if qualified-idkey_compare::is_transparentis valid and denotes a type.这意味着只有当你使用的比较器类型中定义了is_transparent这个类型成员例如std::less、std::greater等透明函数对象时模板版本才会进入重载决议否则编译器会忽略这些重载。透明查找的价值在于避免构造临时key_type对象例如容器存的是std::string你可以直接用const char*或std::string_view作为参数调用find而无需先构造一个临时std::string参与比较从而省去一次堆分配与拷贝。示例使用透明比较器#include oneapi/tbb/concurrent_set.h #include string oneapi::tbb::concurrent_multisetstd::string, std::less names; // 直接以字符串字面量查找无需构造临时 std::string if (names.contains(mold)) { // ... } auto it names.find(mold); auto n names.count(mold);在源码层面这些模板重载全部由is_transparent特征 配合std::enable_if门控例如template typename K typename std::enable_ifis_transparentK::value, iterator::type find( const K key ) { return iterator(internal_find(key)); }当key_compare::is_transparent不存在时is_transparentK::value为假std::enable_if使该重载被 SFINAE 移除从而保证不会与key_type版本产生歧义也不会接受类型不安全的隐式转换。实现原理并发跳表上的查找路径concurrent_multiset并非基于红黑树或 B 树而是并发跳跃表concurrent skip list。这一点可以直接从 concurrent_set.h 的类定义确认template typename Key, typename Compare std::lessKey, typename Allocator tbb::tbb_allocatorKey class concurrent_multiset : public concurrent_skip_listset_traitsKey, Compare, geometric_level_generator32, Allocator, true {几个值得注意的实现要点多重映射开关set_traits的第五个模板参数AllowMultimapping true而concurrent_set为false这是两者共享同一套跳表基类、行为却不同的根本原因。allow_multimapping直接决定了find走internal_find_multi还是internal_find_unique、count是否计算区间长度、equal_range是否需要高层跳跃找上界。随机层级节点层级由geometric_level_generator32生成最大层级上限为 32保证查找的期望复杂度为 O(log n)。免锁并发跳表节点指针使用原子操作维护例如my_head_ptr.load(std::memory_order_relaxed)、my_max_height.load(std::memory_order_acquire)查找过程只读遍历因此可以与并发插入安全共存而删除操作会改变节点链接结构故被排除在并发安全操作之外这从实现层面解释了文档开头查找可与安全修改并发、但与删除不保证并发的约定。无锁查找的代价由于并发插入随时可能改变跳表结构multiset 的internal_find_multi只能在高层路径上先到先得地返回一个等价节点无法保证返回的是最小等价元素——这正是文档中 find返回哪一个元素未指明在实现层面的体现。若需要确定性语义应使用equal_range或lower_bound组合。实战建议与选型对照综合以上 API 语义与实现给出针对concurrent_multiset查找场景的实践建议需求推荐 API说明仅判断 key 是否存在contains语义最清晰内部等价于find ! end()统计等价元素个数countmultiset 场景返回 ≥0 的整数获取任意一个等价元素find不保证返回哪个等价元素获取全部等价元素equal_range返回{f, l}闭开区间可遍历或 distance范围查询区间扫描lower_boundupper_bound经典二分边界组合可用于构建自定义范围避免构造临时 key模板重载 std::less等透明比较器前提是key_compare::is_transparent有效实践要点多重等价元素的处理由于 multiset 允许重复find的结果不确定业务上依赖具体拿到哪一个时应改用equal_range返回的f第一个等价元素或遍历整个区间。并发删除的边界查找 API 的并发安全保证不覆盖unsafe_erase/unsafe_extract/clear。若程序需要在并发修改包含删除的场景下工作需要外部同步机制或改用支持并发删除的容器。测试验证仓库测试 test_concurrent_set.cpp 覆盖了 multiset 的 count 多线程语义4 线程各插 1 份count 必须为 4可作为自己编写并发查找测试的参考模板。本文涉及的规范原文与实现源码均位于当前仓库中规范文档 lookup.rst 与容器总述 concurrent_multiset_cls.rst、头文件 concurrent_set.h、核心实现 _concurrent_skip_list.h以及测试 test_concurrent_set.cpp读者可对照阅读以获取最权威的细节。【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

YOLOv10水下机器人目标识别系统设计与优化

YOLOv10水下机器人目标识别系统设计与优化

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

2026/9/14 9:55:54 阅读更多 →
强化学习入门:从基础原理到实战应用

强化学习入门:从基础原理到实战应用

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

2026/9/14 9:54:54 阅读更多 →
MQTT已连接却无法语音?音频通道与协议选择的排查之道

MQTT已连接却无法语音?音频通道与协议选择的排查之道

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

2026/9/14 9:54:54 阅读更多 →

最新新闻

使用 Rube MCP 在 Codex 中自动化 Supadata 数据操作:Composio 工具链实战指南

使用 Rube MCP 在 Codex 中自动化 Supadata 数据操作:Composio 工具链实战指南

使用 Rube MCP 在 Codex 中自动化 Supadata 数据操作:Composio 工具链实战指南 【免费下载链接】awesome-codex-skills A curated list of practical Codex skills for automating workflows across the Codex CLI and API. 项目地址: https://gitcode.com/GitHub…

2026/9/15 11:58:53 阅读更多 →
Vue智慧水务系统开发实践:从实时监控大屏到项目部署

Vue智慧水务系统开发实践:从实时监控大屏到项目部署

简介:基于 Vue 构建的智慧水务系统完整项目资料包,整合了前端交互界面、业务逻辑与详细开发文档,适合计算机相关专业学生用于毕业设计、课程设计、项目初期演示,也可供开发者作为智慧大屏类工程的起步模板。压缩包共含二百三十个文…

2026/9/15 11:58:53 阅读更多 →
Surya 2 如何通过调整 DPI 设置平衡 OCR 吞吐量与准确率?

Surya 2 如何通过调整 DPI 设置平衡 OCR 吞吐量与准确率?

Surya 2 如何通过调整 DPI 设置平衡 OCR 吞吐量与准确率? 【免费下载链接】surya OCR, layout analysis, reading order, table recognition in 90 languages 项目地址: https://gitcode.com/GitHub_Trending/su/surya 用 Surya 2 的 surya_ocr 批量处理 PDF…

2026/9/15 11:58:53 阅读更多 →
构建高质量QA知识库:语料加工、混合检索与Agentic QA实践

构建高质量QA知识库:语料加工、混合检索与Agentic QA实践

1. 为什么大多数 QA 知识库“建了没人用”:先想清楚三个前置问题先说一个我见得太多的场景:团队花了两三个月,把几千条“问题-答案”整整齐齐地导进系统,上线那天士气很高,结果一个月后看后台数据——用户提问量每天不…

2026/9/15 11:58:53 阅读更多 →
Flowable Docker 基础镜像全解析:基于 Zulu OpenJDK 21 的运行时安全加固与镜像构建实践

Flowable Docker 基础镜像全解析:基于 Zulu OpenJDK 21 的运行时安全加固与镜像构建实践

Flowable Docker 基础镜像全解析:基于 Zulu OpenJDK 21 的运行时安全加固与镜像构建实践 【免费下载链接】flowable-engine A compact and highly efficient workflow and Business Process Management (BPM) platform for developers, system admins and business …

2026/9/15 11:58:53 阅读更多 →
GPS信号产生、捕获与追踪:从C/A码到中频采样的完整链路解析

GPS信号产生、捕获与追踪:从C/A码到中频采样的完整链路解析

简介:面向全球定位系统与软件无线电学习者的MATLAB完整程序包,覆盖信号产生、捕获、追踪三阶段,适用于通信工程、导航技术、嵌入式系统等方向的原理验证与算法实践。压缩包内共7个m文件,按主流程组织:计算C/A码与环路系…

2026/9/15 11:57:53 阅读更多 →

日新闻

Java高级技术:从语言特性到性能优化全解析

Java高级技术:从语言特性到性能优化全解析

1. Java高级技术概述Java作为一门成熟的编程语言,经过二十多年的发展已经形成了完整的生态系统。在企业级应用开发、大数据处理、移动开发等领域,Java都占据着重要地位。掌握Java高级技术不仅意味着能够编写更高效的代码,更代表着开发者能够解…

2026/9/15 0:00:23 阅读更多 →
C#与Halcon结合的工业视觉处理实战指南

C#与Halcon结合的工业视觉处理实战指南

1. 项目概述:C#与Halcon强强联合的视觉处理利器这个基于C#和Halcon的视觉处理Demo项目,是我在工业质检领域摸爬滚打多年后提炼出的实战精华。它完美融合了C#的界面开发优势与Halcon强大的图像处理能力,就像给视觉工程师配上了一把瑞士军刀。项…

2026/9/15 0:00:23 阅读更多 →
32路工业串口服务器的硬核选型指南:确定性、鲁棒性与协议下沉

32路工业串口服务器的硬核选型指南:确定性、鲁棒性与协议下沉

1. 为什么“32路复合型”不是营销话术,而是工业现场真实痛点的硬解你有没有遇到过这样的场景:在某大型能源站的PLC机柜里,十几台不同年代、不同品牌的温控仪、电表、气体分析仪、阀门控制器,全靠RS-485总线挂在一根线上&#xff0…

2026/9/15 0:00:23 阅读更多 →

周新闻

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验 【免费下载链接】ai The AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and ag…

2026/9/14 5:45:49 阅读更多 →
Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化

Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化

Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化 【免费下载链接】refine A React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility. 项目地址: https://gitcode.com/GitH…

2026/9/15 1:32:25 阅读更多 →
Flutter应用改名全指南:从Android到iOS的配置与工具实践

Flutter应用改名全指南:从Android到iOS的配置与工具实践

刚接一个外包项目时,甲方要求把工程里临时用的应用名改成正式产品名。我本来觉得“改名”这种小事,打开配置文件改一行不就完了?结果真动手才发现,Flutter项目里“应用名称”根本不是一处配置,而是一整套散落在 Androi…

2026/9/15 1:32:21 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/14 17:35:10 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/14 16:59:29 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/14 5:45:14 阅读更多 →