oneTBB concurrent_hash_map 实战:用 count_strings 示例掌握并发哈希表与词频统计

📅 发布时间:2026/10/8 14:10:20
oneTBB concurrent_hash_map 实战:用 count_strings 示例掌握并发哈希表与词频统计
并发编程高性能计算【免费下载链接】oneTBBoneAPI Threading Building Blocks (oneTBB)项目地址https://gitcode.com/gh_mirrors/on/oneTBB点击查看免费下载oneTBBoneAPI Threading Building Blocks提供了多种并发容器其中concurrent_hash_map是在多线程下安全执行“查找—更新”操作的标准选择。本篇文章以仓库中examples/concurrent_hash_map目录下的count_strings示例为核心完整讲解其从构建、运行、命令行参数到源码实现的全过程你将掌握如何用 CMake 快速构建 oneTBB 示例、如何通过预定义 make 目标一键运行与性能测量以及concurrent_hash_map的accessor机制、parallel_for搭配与时间测量等关键编程模式并可直接把同样的写法迁移到自己的词频统计、计数聚合等真实业务中。示例概述count_strings 统计文本中的唯一单词examples/concurrent_hash_map/README.md明确指出该目录包含concurrent_hash_map容器的代码示例目前只有一个示例count_strings——并发地将字符串插入concurrent_hash_map容器。示例的目标非常清晰统计一段文本中每个单词的出现次数并报告单词总数与唯一单词数。示例名称描述count_strings并发地将字符串插入concurrent_hash_map容器示例的完整说明见 count_strings 的 README核心实现见 count_strings.cpp。它模拟了一个典型场景大量单词由程序随机生成模拟英文词频分布随后多个线程并发地将它们写入哈希表每个单词作为键Key出现次数作为值Value。这正是concurrent_hash_map最经典的用法——计数聚合count aggregation。构建示例两行 CMake 命令count_strings使用 CMake 构建count_strings 的 README 给出了最简步骤cmake path_to_example cmake --build .其中path_to_example指向示例源码目录即仓库中的examples/concurrent_hash_map/count_strings。第一条命令完成 CMake 配置会通过 common.cmake 查找本仓库构建出的 oneTBB 库第二条命令编译生成可执行文件count_strings。CMakeLists.txt 揭示了构建细节值得注意的点有支持cmake_minimum_required(VERSION 3.5.0...3.31.3)的版本范围写法通过include(../../common/cmake/common.cmake)与set_common_project_settings(tbb)复用所有示例通用的构建配置target_link_libraries(count_strings TBB::tbb Threads::Threads)显式链接 oneTBB 线程库与系统线程库target_compile_options(count_strings PRIVATE ${TBB_CXX_STD_FLAG})应用 oneTBB 要求的 C 标准编译选项。如果你希望先构建整个 oneTBB 库再用示例验证可按仓库顶层 README.md 与 INSTALL.md 的流程构建安装 oneTBB随后在示例目录执行上述两条命令。运行示例预定义 make 目标与应用参数预定义 make 目标示例 CMake 通过add_execution_target生成了两个便捷目标见 CMakeLists.txtmake run_count_strings—— 以预定义参数执行示例ARGS为空即使用默认参数make perf_run_count_strings—— 以测量 oneTBB 性能的建议参数执行示例其参数为auto 10000000 silent自动线程数 1000 万个字符串 静默模式。从PERF_ARGS auto 10000000 silent可以推断性能模式会使用平台默认线程数处理一千万规模的字符串并关闭除耗时以外的所有输出以获得干净的性能数据。命令行参数直接运行编译出的count_strings可执行文件时用法如下摘自 READMEcount_strings [n-of-threadsvalue] [n-of-stringsvalue] [verbose] [silent] [count_collisions] [-h] [n-of-threads [n-of-strings]]各参数含义参数说明-h打印命令行选项帮助n-of-threads使用的线程数支持low[:high]区间形式low 与可选的 high 为非负整数或写auto表示使用平台默认线程数n-of-strings生成的字符串单词数量verbose在屏幕上打印诊断输出每个单词及其计数silent除耗时外不输出任何内容实际使用示例# 使用 4 个线程、100 万个字符串静默模式 ./count_strings n-of-threads4 n-of-strings1000000 silent # 位置参数形式2 个线程50 万个字符串 ./count_strings 2 500000 # 线程数区间 详细输出从 1 到 8 个线程依次测量 ./count_strings 1:8 verbose # 自动线程数1000 万字符串静默等价于 perf 目标 ./count_strings auto 10000000 silent参数解析的源码原理命令行解析并非手写判断而是复用 utility.hpp 中通用的utility::parse_cli_arguments与utility::cli_argument_pack。在 count_strings.cpp 的main中positional_arg(threads, n-of-threads, ...)与positional_arg(N, n-of-strings, ...)注册两个位置参数arg(verbose, verbose, ...)、arg(silent, silent, ...)、arg(count_collisions, count_collisions, ...)注册三个布尔开关。thread_number_range同样定义在 utility.hpp支持解析low[:high[:(|*|#)step]]形式的区间表示线性递增、*表示倍增、#表示2 的幂阶梯步进默认步进即#4并且auto会替换为range.auto_number_of_threads()的返回值——本示例传入utility::get_default_num_threads其实现见 get_default_num_threads.hpp就是oneapi::tbb::this_task_arena::max_concurrency()即平台可用的最大并发度。核心源码解读accessor 机制与并发计数关键类型定义// 字符串类型使用 oneTBB 的可扩展分配器 typedef std::basic_stringchar, std::char_traitschar, oneapi::tbb::tbb_allocatorchar MyString; // 并发哈希表键为字符串值为出现次数 typedef oneapi::tbb::concurrent_hash_mapMyString, int StringTable;代码位于 count_strings.cpp 顶部。使用tbb_allocator分配字符串内存能让所有字符串的内存分配也走 oneTBB 的可扩展内存分配器减少堆竞争。Tally 函数对象插入与自增struct Tally { StringTable table; Tally(StringTable table_) : table(table_) {} void operator()(const oneapi::tbb::blocked_rangeMyString* range) const { for (MyString* p range.begin(); p ! range.end(); p) { StringTable::accessor a; table.insert(a, *p); a-second 1; } } };这是示例的核心模式对每个单词调用table.insert(a, *p)insert返回true表示键不存在、已插入新条目返回false表示键已存在、accessor指向已有条目。随后通过a-second 1完成并发安全的计数自增。这里的关键是accessor机制。查看 concurrent_hash_map.h 的声明约第 771—835 行可以看到accessor继承自const_accessor注释说明它结合了数据访问、加锁与垃圾回收Combines data access, locking, and garbage collectionaccessor允许写访问reference operator*()、pointer operator-()而const_accessor只允许只读访问每个accessor在持有期间会对对应条目加锁析构时自动释放锁与引用保证读改写read-modify-write过程的原子性——这正是concurrent_hash_map与concurrent_unordered_map的重要差异前者通过 accessor 提供元素级锁语义适合查找后更新的并发模式。concurrent_hash_map还提供完整的find/insert/erase重载族支持键、值、迭代器区间、initializer_list 等详见同一头文件的find约 1112—1138 行、insert约 1140—1236 行与erase约 1241—1259 行。并行执行与计时static void CountOccurrences(int nthreads) { StringTable table; oneapi::tbb::tick_count t0 oneapi::tbb::tick_count::now(); oneapi::tbb::parallel_for( oneapi::tbb::blocked_rangeMyString*(Data, Data N, 1000), Tally(table)); oneapi::tbb::tick_count t1 oneapi::tbb::tick_count::now(); // ... 遍历 table累加 total 并统计 unique ... }blocked_rangeMyString*(Data, Data N, 1000)把N个字符串切分成粒度约 1000 的连续块交给parallel_for自动调度tick_count::now()是 oneTBB 提供的高精度计时 API(t1 - t0).seconds()给出并行阶段耗时结束后遍历table累加出现次数得到totaltable.size()即唯一单词数unique输出形如total 1000000 unique 27180 time 0.046。线程数控制global_control示例通过oneapi::tbb::global_control c(oneapi::tbb::global_control::max_allowed_parallelism, p)精确限制并行度。main中的运行逻辑为若显式给出了线程数区间则从threads.first到threads.last按步进逐个测量并打印threads N若未指定threads.first 0先以max_allowed_parallelism1执行一次串行运行再以utility::get_default_num_threads()执行一次自动并行运行——串行与并行对比便于直观感受加速比。数据生成模拟英文词频的随机单词为了让词频统计更像真实文本示例自带了一个基于音素频率表的随机单词生成器CreateData及Vowels/Consonants表见 count_strings.cpp。其思路是维护元音与辅音字母组合表每项带rates[3]词首、词中、词尾三个位置的出现权重按权重随机选取组合拼成单词使生成的单词呈现接近自然语言的分布——既有大量重复高频词也有不少长尾唯一词生成后还会构造一句彩蛋消息打印出来。这样的数据让count_strings既能展示同一键多次命中的并发自增路径也能展示唯一键大量插入的扩容路径比均匀随机数据更贴近实际负载。可选诊断统计哈希碰撞示例还提供了一个可选开关count_collisions启用后会对表中每个唯一键的哈希值取掩码std::hashMyString()(i-first) 0xFFFF统计分布并打印hashes N collisions M。源码注释特别提醒该统计并不反映哈希表内部真实桶碰撞it doesnt count real collisions in hash_map, a mask should be applied on hash value仅作为哈希值分布的粗略诊断工具。它是std::mapstd::size_t, int hashes与全局计数c实现的每次测量后会被清空因此只适合小规模定性观察。更多验证途径想深入验证concurrent_hash_map的全部 API 与语义可阅读一致性测试 conformance_concurrent_hash_map.cpp若要了解容器在并发读写、遍历与扩容等场景下的正确性保证可在 oneTBB 文档目录 concurrent_hash_map.rst 找到对应的用户指南章节官方 API 参考见 include/oneapi/tbb/concurrent_hash_map.h 中的完整类定义与注释。小结count_strings是一个麻雀虽小、五脏俱全的 oneTBB 示例它用concurrent_hash_mapaccessor解决了多线程下的计数聚合问题用parallel_forblocked_range实现了负载切分用tick_count完成高精度计时用global_control精确控制并行度并用一套通用 CLI 解析框架统一了参数体验。无论你是要统计日志中的关键词、构建词云还是做任何键出现次数类的并发聚合任务都可以直接参考 count_strings.cpp 的模式落地。赞分享并发编程高性能计算【免费下载链接】oneTBBoneAPI Threading Building Blocks (oneTBB)项目地址https://gitcode.com/gh_mirrors/on/oneTBB点击查看免费下载相关推荐oneTBB concurrent_hash_map 并发哈希表完全指南读写访问控制、HashCompare 定制与并行词频统计实战oneTBB concurrent_hash_map 并发哈希表完全指南读写访问控制、HashCompare 定制与并行词频统计实战 concurrent_h并发编程高性能计算并发哈希表优化oneTBB concurrent_hash_map应用指南并发哈希表优化oneTBB concurrent_hash_map应用指南 引言高性能并发数据结构的必要性 在多核处理器主导的时代传统的线程安全哈希表如并发编程高性能计算oneTBB concurrent_hash_map 自定义 HashCompare 全指南从默认 tbb_hash_compare 到实例级哈希比较oneTBB concurrent_hash_map 自定义 HashCompare 全指南从默认 tbb_hash_compare 到实例级哈希比较 本篇技并发编程高性能计算上一篇华硕主板风扇控制难题全解析让FanControl完美识别你的硬件下一篇Dayspan-Vuetify为什么它是现代Vue.js应用中最值得投资的日历解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考