拓冰建站拓冰建站
首页 / 资讯中心 / 正文

TBB concurrent_unordered_multimap 并发安全修改器详解:emplace、insert、node handle 与 merge 的完整用法与实现原理

TBB concurrent_unordered_multimap 并发安全修改器详解emplace、insert、node handle 与 merge 的完整用法与实现原理【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold本文围绕 oneTBB 并发容器规范中concurrent_unordered_multimap的 Concurrently safe modifiers并发安全修改器章节展开完整覆盖emplace、各类insert重载、insert(InputIterator, InputIterator)序列插入、node handle 插入以及merge容器合并的全部签名与语义并结合 oneTBB 头文件实现说明这些接口为何可以在多线程下并发调用、hint参数为何被忽略、以及merge内部摘节点再插回的无复制转移机制。读完本文你可以放心地在多线程程序中对该容器做高并发写入并准确理解每种重载的行为边界、返回值含义与未定义行为前提。一、并发安全修改器的使用契约规范文档 safe_modifiers.rst 对这一节给出的第一条总约定是本节中的所有成员函数既可以相互并发执行也可以与查找方法并发执行还可以与容器遍历并发执行。这是concurrent_unordered_multimap与 C11 时代旧容器concurrent_hash_map相比的关键改进旧容器要求修改操作必须串行执行而新容器对应头文件 concurrent_unordered_map.h把插入/合并归入并发安全类把clear()、unsafe_erase、unsafe_extract、swap等破坏遍历语义的操作归入并发不安全类见 unsafe_modifiers.rst其开篇明确说明这些成员函数只能串行执行与任何其他方法并发执行时行为未定义。因此实践中的第一条规则是写入用本节接口删除与清空走 unsafe 接口并确保无并发访问。另一个 multimap 特有的语义是与concurrent_unordered_map不同multimap 允许同一个 key 存在多个元素因此所有返回std::pairiterator, bool的插入接口中布尔值恒为true——文档中每一个相关重载都明确写明了 Boolean value is alwaystrue。这在实现中对应 concurrent_unordered_map.h 中 multimap 以allow_multimapping true特化为基类模板参数插入路径不会因 key 已存在而失败。二、emplace原地构造元素template typename... Args std::pairiterator, bool emplace( Args... args );从args原地构造一个元素并插入容器避免先构造再拷贝/移动的开销返回std::pairiterator, booliterator指向插入的元素布尔值恒为true。template typename... Args iterator emplace_hint( const_iterator hint, Args... args );同样原地构造并插入hint作为节点应放置位置的建议返回指向插入元素的iterator。源码印证hint 是被忽略的。在基类实现 _concurrent_unordered_base.h 中template typename... Args iterator emplace_hint( const_iterator, Args... args ) { // Ignore hint return emplace(std::forwardArgs(args)...).first; }从源码结构看所有带hint的重载都直接转发到不带 hint 的版本。这是由容器内部按 split order keySO key排序的双向链表 多级索引的布谷鸟式组织方式决定的节点最终落点由哈希值推导的顺序键决定外部提示无法介入无锁定位过程。文档说Optionally uses the parameterhintas a suggestion实现上则选择了兼容标准接口签名、实际忽略 hint 的折中读者不应指望 hint 带来任何性能收益。emplace的核心实现在 _concurrent_unordered_base.h#L468-L488先用临时 order key 0 创建节点再调用internal_insert若插入失败仅 map 情形multimap 不会发生则销毁刚创建的节点。对 multimap 而言节点一旦创建必然成功入链因此emplace在 multimap 上是无失败路径的并发安全写入。三、insert值语义的六种重载规范文档按四种形态列出了值插入接口全部可并发执行3.1 左值 / 右值拷贝形态std::pairiterator, bool insert( const value_type value );将value插入容器返回std::pairiterator, bool布尔值恒为true。iterator insert( const_iterator hint, const value_type other );同上的 hint 版本hint仅为放置建议返回指向插入元素的iterator。std::pairiterator, bool insert( value_type value );使用移动语义插入value被置于有效但不指明的状态返回值同上布尔值恒为true。iterator insert( const_iterator hint, value_type other );移动语义 hint 建议返回插入元素的iteratorvalue被置于有效但不指明的状态。源码印证基类中这四者分别转发到internal_insert_value_concurrent_unordered_base.h#L415-L431带 hint 的两个重载同样以// Ignore hint注释表明提示被忽略。实现注释解释了internal_insert的两阶段设计先无锁搜索插入点再在找到插入位置之后才调用回调创建节点见 L973-L987这保证了失败时不会残留已分配节点。3.2 万能引用转发形态template typename P std::pairiterator, bool insert( P value );等价于emplace(std::forwardP(value))仅当std::is_constructiblevalue_type, P::value为true时参与重载决议SFINAE 约束。template typename P iterator insert( const_iterator hint, P value );等价于emplace_hint(hint, std::forwardP(value))同样受std::is_constructiblevalue_type, P约束。这正是 concurrent_unordered_map.h#L289-L299 中 multimap 的insert(P)模板成员——用std::enable_if实现文档描述的参与重载决议的条件函数体一行return this-emplace(std::forwardP(value));与文档等价于 emplace的表述逐字对应。这一重载让你可以直接insert(std::make_pair(k, v))或insert({k, v})而无需写出value_type完整类型。3.3 重载选择建议场景推荐重载原因从已有 key/value 对象就地构造emplace(k, v)直接构造节点无中间对象已有const value_typeinsert(value)语义最直白已有右值value_typeinsert(std::move(value))移动而非拷贝临时 pair / brace-init模板insert(P)SFINAE 自动匹配想要标准库风格hint任意 hint 版本签名兼容但实现忽略 hint四、插入元素序列template typename InputIterator void insert( InputIterator first, InputIterator last );将半开区间[first, last)内的所有元素插入容器要求InputIterator必须满足 ISO C 标准[input.iterators]一节中 InputIterator 的要求即最低档迭代器即可不要求随机访问。void insert( std::initializer_listvalue_type init );等价于insert(init.begin(), init.end())。源码印证基类实现_concurrent_unordered_base.h#L433-L442就是对区间逐个insert(*first)的简单循环。由于单次插入本身并发安全批量插入在 multimap 中也天然不会失败遍历完即全部就位。注意文档未声明批量插入是单操作原子——从其他线程观察可能只看到部分元素已插入这是所有并发容器的常态。五、插入 node handle节点句柄std::pairiterator, bool insert( node_type nh );若nh为空则什么都不做否则将nh所拥有的节点插入容器插入后nh变为空状态不会调用value_type的拷贝或移动构造——节点直接在链表中重新链接若nh非空且get_allocator() ! nh.get_allocator()行为未定义返回std::pairiterator, bool布尔值恒为true空 handle 情形返回{end(), false}见实现 L444-L461。iterator insert( const_iterator hint, node_type nh );同上的 hint 版本hint作为放置建议空 handle 什么都不做返回指向插入元素的iterator同样不执行任何拷贝/移动构造分配器不等则行为未定义。node handle 的引入使摘出—暂存—再插入成为零拷贝操作也是merge的底层机制。与unsafe_extract见 unsafe_modifiers.rst配合时需注意提取本身不安全但提取后把 handle 通过并发安全的insert(node_type)放回任意容器是合法的组合模式。六、merge容器合并规范给出四个重载覆盖目标容器 × 源容器与 map/multimap 的任意组合template typename SrcHash, typename SrcKeyEqual void merge( concurrent_unordered_mapKey, T, SrcHash, SrcKeyEqual, Allocator source ); template typename SrcHash, typename SrcKeyEqual void merge( concurrent_unordered_mapKey, T, SrcHash, SrcKeyEqual, Allocator source ); template typename SrcHash, typename SrcKeyEqual void merge( concurrent_unordered_multimapKey, T, SrcHash, SrcKeyEqual, Allocator source ); template typename SrcHash, typename SrcKeyEqual void merge( concurrent_unordered_multimapKey, T, SrcHash, SrcKeyEqual, Allocator source );语义将source中的所有元素转移到*this全程不执行value_type的拷贝或移动构造节点直接在两个容器间重新链接若get_allocator() ! source.get_allocator()行为未定义模板参数SrcHash/SrcKeyEqual说明源容器可以与目标使用不同的哈希器与等价谓词只要 key/mapped 类型相同。对应源码在 concurrent_unordered_map.h#L301-L319multimap 的四个merge重载全部转发给基类的internal_merge。而internal_merge_concurrent_unordered_base.h#L1197-L1246的实现细节值得细读直接遍历源的节点链表for (node_ptr source_prev source.my_head; ...)跳过 dummy 索引节点逐节点处理——因此merge对源容器是不安全的规范要求串行调用实现中的__TBB_ASSERT(source_prev-next() next_node, Concurrent operations with the source container in merge are prohibited)断言印证了这一点multimap 全量转移判断条件为allow_multimapping || !contains(key)——对 multimap 恒真所以源中每个节点都会被unlink_node摘出与文档Transfers all elements一致对 map 则 key 已存在时节点留在源中通过 node handle 插入摘出的节点用node_handle_accessor::constructnode_type(curr)包成 handle 后走并发安全的insert(node_type)这正是第五节接口的内部复用全程零拷贝失败回滚map 场景下若插入失败节点以旧 order key 重新插回源链表并继续——实现注释特别说明merge 对源容器是不安全的所以回插无需 compare_exchange原子计数每成功转移一个节点source.my_size.fetch_sub(1, std::memory_order_relaxed)源容器size()随之单调下降目标容器size()单调上升。实践含义merge是把 N 个 per-thread 容器汇成一个全局容器的高效手段——N 个线程各自向本地 multimap 无锁写入收尾时主线程串行merge全部本地容器元素零拷贝归位。七、并发安全性的来源与使用建议从源码结构看本节所有接口之所以可以互相并发是因为底层插入是 CAS 链式操作internal_insert先按 SO key 定位prev/curr区间再用静态函数try_insertL1076 附近尝试把新节点链接进prev与预期后继之间失败则重试或分裂索引桶。插入只影响局部链表段不破坏其他线程正在进行的查找与遍历这正是can be performed concurrently with each other, lookup methods and while traversing the container能成立的原因。使用这些接口时的几条建议写入一律用本节接口emplace/insert/insert(node handle)/merge它们可以相互并发、可以与find/bucket等查找lookup.rst及parallel_for_each遍历parallel_iteration.rst同时进行删除、清空、swap 走 unsafe 系列unsafe_modifiers.rst调用前必须保证没有线程在并发使用该容器不要为 hint 付费带 hint 的重载只是 API 兼容形态实现直接忽略 hint选择重载时按数据形态左值/右值/转发即可node handle 与 merge 要求分配器等价get_allocator()不等即未定义行为跨容器搬运节点时请使用相同分配器实例默认tbb::tbb_allocatorstd::pairconst Key, T满足此条件multimap 的插入不会失败pairiterator, bool的bool恒为true不需要像标准unordered_multimap的 map 变体那样检查插入结果。八、小结concurrent_unordered_multimap的并发安全修改器集合由五组接口构成emplace/emplace_hint提供原地构造写入六组值语义insert覆盖拷贝、移动与万能引用转发其中两个模板重载由std::is_constructibleSFINAE 约束参与重载决议insert(InputIterator, InputIterator)与insert(initializer_list)提供批量写入insert(node_type)及其 hint 版本提供零拷贝节点移植merge的四个重载实现跨容器的全量零拷贝合并。所有接口可相互并发、可与查找和遍历并发这是由底层 CAS 链表插入与 SO key 索引结构保证的而merge内部摘链—handle 包装—并发插入—失败回滚的流水线则与 node handle 接口共享同一条实现路径。掌握上述签名、返回值语义与分配器等价前提即可在多线程场景下安全、高效地使用该容器。【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门