
mold 项目中的 TBB concurrent_hash_map 查找操作指南find 与 count 的并发语义与源码实现【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold导读本文以 oneTBBThreading Building Blocks规范文档中concurrent_hash_map的Lookup章节为骨架系统讲解find与count两个核心查找操作的完整 API 形态、返回值语义、透明查找transparent lookup参与条件并结合本仓库内 oneTBB 源码实现与 mold 链接器 src/mapfile.cc 的实际使用方式深入剖析其底层的分段哈希表、桶级读写锁与访问器accessor机制。读完本文你将掌握如何在多线程场景下安全、高效地使用concurrent_hash_map完成查找并理解为什么这些查找方法可以与其他并发安全修改操作自由交错执行。1. Lookup 章节在规范中的定位在 oneTBB 规范文档中concurrent_hash_map的方法按功能划分为多个章节其中 lookup.rst 专门描述只读性质的成员查找操作。该章节开头即给出了一条全局性的并发安全保证All methods in this section can be executed concurrently with each other and concurrently-safe modifiers.即本节Lookup中的所有方法可以彼此并发执行也可以与concurrent_hash_map中所有并发安全修改器如insert、erase等同时执行。这意味着find与count都可以放心地在多线程环境调用无需额外的外部同步。对应到实际实现这条保证的基石是concurrent_hash_map内部的分段哈希表结构哈希表按 key 的哈希值划分为若干 segment每个 segment 内部又包含带读写锁的 bucket桶查找操作只需对目标桶加读锁即可完成修改操作则以桶为粒度加写锁因此不同桶上的操作天然互不阻塞。本节共包含两组 APIfind返回访问器与count返回元素个数。2. find查找并获取元素访问器2.1 基础重载与返回值语义规范文档给出了两个基础重载bool find( const_accessor result, const key_type key ) const; bool find( accessor result, const key_type key );其语义分三层释放已有访问器如果传入的result访问器不为空即此前已关联到某个元素find会先释放该result。设置访问器如果容器中存在与key等价的元素则将result设置为指向该元素从而通过访问器获得对元素尤其是其second值的访问能力。返回值找到则返回true否则返回false。两个重载的差异在于获得的访问权限重载访问器类型获得锁用途find(const_accessor, key)const 方法const_accessor读锁只读访问元素值find(accessor, key)非 const 方法accessor写锁需要修改元素second值在 concurrent_hash_map.h 中两个重载的实现均先调用result.release()再委托给内部模板函数lookup唯一的区别是写标志// Find item and acquire a read lock on the item. bool find( const_accessor result, const Key key ) const { result.release(); return const_castconcurrent_hash_map*(this)-lookup/*insert*/false( key, nullptr, result, /*write*/false, do_not_allocate_node ); } // Find item and acquire a write lock on the item. bool find( accessor result, const Key key ) { result.release(); return lookup/*insert*/false(key, nullptr, result, /*write*/true, do_not_allocate_node); }注意lookup的模板参数OpInsertfalse查找操作绝不插入新节点、绝不分配内存这与insert系列接口复用同一核心路径形成对比。2.2 透明查找重载C 模板形式规范文档还给出了泛型重载template typename K bool find( const_accessor result, const K key ) const; template typename K bool find( accessor result, const K key );该重载允许传入与key_type不同类型的查找键前提是键比较器支持透明比较。规范明确限定其参与条件This overload only participates in the overload resolution if qualified-idhash_compare_type::is_transparentis valid and denotes a type.即只有当hash_compare_type中声明了is_transparent类型如using is_transparent void;时该模板重载才参与重载决议。源码中通过 SFINAE 实现这一约束concurrent_hash_map.htemplate typename K typename std::enable_ifhash_compare_is_transparentK::value, bool::type find( const_accessor result, const K key ) { result.release(); return lookup/*insert*/false(key, nullptr, result, /*write*/false, do_not_allocate_node); }hash_compare_is_transparent的定义concurrent_hash_map.h最终落到 _containers_helpers.h 中的comp_is_transparent特征类它通过检测比较器是否包含is_transparent成员类型来判定template typename Compare, typename void struct comp_is_transparent : std::false_type {}; template typename Compare struct comp_is_transparentCompare, tbb::detail::void_ttypename Compare::is_transparent : std::true_type {};典型应用场景当key_type是std::string时若哈希/比较器声明了is_transparent就可以直接用const char*或std::string_view查找避免构造临时std::string带来的分配开销——这正是透明查找的核心价值。可以推断在键类型为字符串、查找频繁的高吞吐场景下这一重载能显著减少堆分配。3. count仅查询是否存在3.1 基础重载size_type count( const key_type key ) const;count返回1如果存在与key等价的元素否则返回0。注意返回值类型是size_type即std::size_t因此结果应视为0 或 1而非元素总数——这是concurrent_hash_map与std::unordered_multimap的关键区别它不允许重复 key。3.2 透明重载与实现template typename K size_type count( const K key ) const;与find的模板重载相同仅当hash_compare_type::is_transparent有效时参与重载决议。源码实现concurrent_hash_map.h将lookup的result参数传为nullptr从而跳过访问器的创建与加锁size_type count( const Key key ) const { return const_castconcurrent_hash_map*(this)-lookup/*insert*/false( key, nullptr, nullptr, /*write*/false, do_not_allocate_node); } template typename K typename std::enable_ifhash_compare_is_transparentK::value, size_type::type count( const K key ) const { return const_castconcurrent_hash_map*(this)-lookup/*insert*/false( key, nullptr, nullptr, /*write*/false, do_not_allocate_node); }从源码结构可以推断count是比find更轻量的操作它不需要try_acquire锁住元素仅需在桶链表上完成一次search_bucket遍历即可返回。4. 源码级原理lookup 核心路径find与count最终都汇入同一个内部函数lookupconcurrent_hash_map.h其完整执行流程如下计算哈希hashcode_type const h my_hash_compare.hash(key);并读取当前的段掩码my_mask使用memory_order_acquire。定位桶通过bucket_accessor b(this, h m)获得目标桶bucket_accessor继承自 bucket 的scoped_type进入桶即持有桶锁读锁或写锁由调用上下文决定。链上搜索调用search_bucket(key, b())在桶的单向链表中线性查找。search_bucket的实现concurrent_hash_map.h非常简单从头节点出发用my_hash_compare.equal逐个比较 key 直到命中或链表结束。命中处理若result非空即find则对命中的节点执行try_acquire(n-mutex, write)获取读锁或写锁失败则进入atomic_backoff自旋退避循环重试若等待过长还会yield()后从restart标签重新开始整个查找。若result为空即count跳过加锁直接返回。掩码竞态检测若check_mask_race(h, m)发现查找期间容器发生了扩容my_mask已变化则从restart重新执行确保看到的桶集合一致。增长调度grow_segment非零时调用enable_segment触发分段扩容仅insert路径会设置该标志查找路径为 0。lookup开头还有一条关键断言__TBB_ASSERT(!result || !result-my_node, nullptr)即调用方必须保证传入的result已释放——这正是规范中如果result访问器不为空则先释放这一条语义在实现层的体现虽然公开 API 已代为执行release()。5. 访问器accessor的锁语义与生命周期find区别于普通容器查找的关键在于返回的访问器本身持有锁。理解访问器类型对正确使用至关重要const_accessorconcurrent_hash_map.h私有继承自node::scoped_type一种 RAII 锁类型提供empty()是否为空访问器与release()释放锁并置空方法。它持有的是读锁多个线程可同时通过各自const_accessor读取同一元素。accessor公开继承自const_accessorconcurrent_hash_map.h额外提供operator*与operator-返回非 const 引用。它持有的是写锁同一时刻只有一个线程能获得该元素的accessor从而保证读-改-写原子性。规范文档虽然没有逐字列出访问器的成员但find的语义描述sets the result to provide access to this element正是建立在这套锁机制之上的。由源码结构可以推断以下使用要点访问器作用域应尽量短访问器持有元素锁若长时间不release()其他线程对同一元素的读写将被阻塞。推荐将访问器限定在最小作用域内或主动调用release()。优先使用const_accessor只需读取时用find(const_accessor, ...)获取读锁避免不必要的写锁竞争。通过empty()判断合法性find返回true后访问器才有效访问器默认构造后为空operator*对空访问器会触发断言源码中__TBB_ASSERT(this-my_node, attempt to dereference empty accessor)。先释放再复用规范规定find会释放非空的result因此循环内复用同一个访问器是安全的不必每次手动release()。6. 并发安全保证的底层支撑回到规范开篇的并发安全声明其成立依赖三套机制桶级读写锁bucket_accessor在查找/修改期间锁定单个桶桶锁粒度小、竞争低天然支持不同桶上的操作并行。节点级互斥锁node内嵌mutex配合try_acquire/atomic_backoff退避实现访问器的锁获取使得find返回后、访问器释放前元素内容仍受保护不会被erase等操作并发销毁。分段扩容的掩码竞态处理check_mask_racerestart机制保证查找与扩容并发时的一致性——若查找过程中容器增长导致掩码变化操作会重试而不是在旧的可能是未 rehash 的桶上错误返回。这三者共同保证了find/count可以与insert/erase等修改器并发执行这一规范承诺这也是concurrent_hash_map区别于需要外部加锁的std::unordered_map的根本所在。7. 规范语义的测试印证仓库测试代码对find/count的语义进行了逐条验证test/tbb/test_concurrent_hash_map.cpp 中check_value测试对每个元素依次验证count 1→find(ca, key)成功且!ca.empty()→ 通过ca-first/ca-second读取值 →erase(ca)后count 0。这一流程完整覆盖了规范中find 设置访问器count 返回 0/1访问器可传递至 erase等语义。同一测试还验证了accessor非 const 访问器路径L148-L161确认写访问器同样可用于查找、删除与再插入。test/common/concurrent_associative_common.h 中的多映射测试则覆盖了count与find在一般关联容器接口含迭代器版find下的一致性。这些测试用例即规范文档所述行为的可执行化表述可作为理解 API 语义的辅助参考。8. 实战mold 链接器中的真实用法本仓库mold——现代链接器在 src/mapfile.cc 中真实使用了tbb::concurrent_hash_map的查找相关机制是理解本文所述 API 的最佳实战案例#include tbb/concurrent_hash_map.h #include tbb/parallel_for_each.h template typename E using Map tbb::concurrent_hash_mapInputSectionE *, std::vectorSymbolE *;在get_map中mold 使用parallel_for_each并行遍历所有目标文件为每个符号的输入节InputSection建立节 → 符号列表的映射。这里用到的正是Map::accessortbb::parallel_for_each(ctx.objs, { for (SymbolE *sym : file-symbols) { if (sym-file file sym-get_type() ! STT_SECTION) { if (InputSectionE *isec sym-get_input_section()) { typename MapE::accessor acc; map.insert(acc, {isec, {}}); // 通过 accessor 获得写权限 acc-second.push_back(sym); // 在锁保护下就地修改值 } } } });这个模式体现了concurrent_hash_map的核心设计价值多线程并发往同一容器插入并就地更新值而无需任何外部锁。insert返回的accessor与find返回的accessor是同一种对象都持有写锁——这正是规范文档中 find 与并发安全修改器可并发执行 的另一面多个线程对同一个 InputSection 的符号列表执行先 insert 再 push_back时由节点锁保证acc-second.push_back(sym)的原子性符号列表绝不会在并发更新下损坏。随后该映射表被print_map用于生成链接 map 文件配合parallel_for(map.range(), ...)对每个节的符号列表并行排序。9. 使用建议与注意事项小结结合规范文档与源码实现使用 Lookup 章节 API 时建议遵循以下实践查询优先用count需要访问值才用findcount不获取节点锁、开销最小find返回的访问器会持有锁若仅判断存在性用count可避免不必要的锁竞争。只读场景选const_accessorfind(const_accessor, ...)获取读锁允许多读者并发只有确实需要修改second时才用find(accessor, ...)。及时释放访问器访问器是 RAII 锁超出作用域自动释放需要提前结束锁保护时调用release()。键类型非平凡时启用透明查找若比较器声明is_transparent直接用string_view/const char*等类型查找避免临时对象构造注意透明重载仅在hash_compare_type::is_transparent存在时可用这是参与重载决议的硬性条件。充分利用并发安全保证find/count可与insert/erase并发执行无需外部互斥在多线程生产者-消费者、并发索引构建如 mold 的 mapfile 生成等场景可放心使用。本文所述 API 的权威定义见 lookup.rst完整源码见 concurrent_hash_map.h仓库内实际使用与测试验证可分别参考 src/mapfile.cc 与 test/tbb/test_concurrent_hash_map.cpp。【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考