ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

mold 内置 TBB:concurrent_unordered_set 的构造、析构与拷贝语义详解

mold 内置 TBB:concurrent_unordered_set 的构造、析构与拷贝语义详解 mold 内置 TBBconcurrent_unordered_set 的构造、析构与拷贝语义详解【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold本文围绕 TBBoneTBB随 mold 仓库以第三方库形式集成在third-party/tbb/目录下容器规范中concurrent_unordered_set的“Construction, destruction, copying”构造、析构、拷贝一节展开完整覆盖空容器构造器、区间/初始化列表构造器、拷贝与移动构造、析构函数以及三类赋值运算符的签名、语义与注意事项并结合 concurrent_unordered_set 头文件 与 内部基类实现 说明这些接口的实际落地方式帮助你安全地在多线程程序中初始化、复制和转移该容器。容器定位与类模板概述concurrent_unordered_set是一个表示唯一元素无序序列的类模板元素按哈希值分桶组织支持并发插入、查找和遍历但不支持并发删除。根据规范文档 concurrent_unordered_set.rst 的类模板摘要其模板参数与关键成员类型如下// Defined in header oneapi/tbb/concurrent_unordered_set.h template typename Key, typename Hash std::hashKey, typename KeyEqual std::equal_toKey, typename Allocator tbb::tbb_allocatorKey class concurrent_unordered_set { using key_type Key; using value_type Key; // 集合中 key 与 value 同型 using size_type /* 无符号整数类型 */; using hasher Hash; using key_equal /* 可能由 Hash::transparent_key_equal 推导 */; using allocator_type Allocator; // iterator / local_iterator / node_type / range_type 等 // 构造、析构、拷贝本文重点 // ... };在仓库源码中该模板定义于 concurrent_unordered_set.h通过继承模板化基类concurrent_unordered_basetraits复用全部存储与并发逻辑allow_multimapping false的 traits 参数决定了它是“唯一元素”的 set 而非 multiset。空容器构造器默认与仅分配器构造concurrent_unordered_set(); explicit concurrent_unordered_set( const allocator_type alloc );语义构造一个空容器初始桶数量未指定unspecified若提供alloc则后续内存分配都使用该分配器。explicit关键字防止从分配器参数意外隐式转换。从 基类实现 看这两个构造器都委托给主构造器且“未指定”的初始桶数实际上有固定取值// _concurrent_unordered_base.h concurrent_unordered_base() : concurrent_unordered_base(initial_bucket_count) {} explicit concurrent_unordered_base( const allocator_type alloc ) : concurrent_unordered_base(initial_bucket_count, hasher(), key_equal(), alloc) {}其中 initial_bucket_count 定义为8且主构造器还会把传入的桶数向上取整为 2 的幂explicit concurrent_unordered_base( size_type bucket_count, const hasher hash hasher(), const key_equal equal key_equal(), const allocator_type alloc allocator_type() ) : my_size(0), my_bucket_count(round_up_to_power_of_two(bucket_count)), my_max_load_factor(float(initial_max_load_factor)), my_hash_compare(hash, equal), my_head(sokey_type(0)), my_segments(alloc) {}即即使规范只说“初始桶数未指定”实现层面也是确定的——空构造得到 8 个桶、初始max_load_factor为 4.0f。指定桶数及哈希/比较参数explicit concurrent_unordered_set( size_type bucket_count, const hasher hash hasher(), const key_equal equal key_equal(), const allocator_type alloc allocator_type() ); concurrent_unordered_set( size_type bucket_count, const allocator_type alloc ); concurrent_unordered_set( size_type bucket_count, const hasher hash, const allocator_type alloc );语义构造一个拥有bucket_count个桶的空容器hash决定元素落桶equal用于判定key_type相等alloc提供内存。三个重载分别对应“全参数”“桶数分配器”“桶数哈希分配器”的组合后两者的key_equal固定取默认std::equal_toKey。结合上面的主构造器代码可以确认两个实现细节实际桶数经过round_up_to_power_of_two(bucket_count)处理因此传入 10 会得到 16 个桶若你使用透明哈希Hash::transparent_key_equal有效来自定义key_equal规范明确要求此时KeyEqual必须保持为默认的std::equal_toKey且Hash::transparent_key_equal::is_transparent必须有效否则程序 ill-formed。从元素序列构造的构造器迭代器区间构造template typename InputIterator concurrent_unordered_set( InputIterator first, InputIterator last, size_type bucket_count /*implementation-defined*/, const hasher hash hasher(), const key_equal equal key_equal(), const allocator_type alloc allocator_type() ); template typename InputIterator concurrent_unordered_set( InputIterator first, InputIterator last, size_type bucket_count, const allocator_type alloc ); template typename InputIterator concurrent_unordered_set( InputIterator first, InputIterator last, size_type bucket_count, const hasher hash, const allocator_type alloc );语义与约束构造一个包含半开区间[first, last)内全部元素的集合若区间内存在多个相等元素最终保留哪一个是不确定的集合只保留唯一值InputIterator必须满足 ISO C 标准 [input.iterators] 一节对输入迭代器的要求bucket_count缺省值为实现定义实现中即initial_bucket_count见下。从源码看这一族构造器在 基类 中的实现是“先委托给四参数主构造器再对整个区间调用insert(first, last)”template typename InputIterator concurrent_unordered_base( InputIterator first, InputIterator last, size_type bucket_count initial_bucket_count, const hasher hash hasher(), const key_equal equal key_equal(), const allocator_type alloc allocator_type() ) : concurrent_unordered_base(bucket_count, hash, equal, alloc) { insert(first, last); }其余两个重载只是用默认hasher()/key_equal()补齐参数后转发说明重复元素的处理交给单元素insert的唯一性逻辑与规范中“unspecified which element would be inserted”完全一致。初始化列表构造concurrent_unordered_set( std::initializer_listvalue_type init, size_type bucket_count /*implementation-defined*/, const hasher hash hasher(), const key_equal equal key_equal(), const allocator_type alloc allocator_type() ); // 等价于 concurrent_unordered_set(init.begin(), init.end(), bucket_count, hash, equal, alloc) concurrent_unordered_set( std::initializer_listvalue_type init, size_type bucket_count, const allocator_type alloc ); // 等价于 concurrent_unordered_set(init.begin(), init.end(), bucket_count, alloc) concurrent_unordered_set( std::initializer_listvalue_type init, size_type bucket_count, const hasher hash, const allocator_type alloc ); // 等价于 concurrent_unordered_set(init.begin(), init.end(), bucket_count, hash, alloc)规范将三个初始化列表重载显式定义为区间构造器的等价格式。源码同样印证了这一点——基类中直接转发concurrent_unordered_base( std::initializer_listvalue_type init, size_type bucket_count initial_bucket_count, const hasher hash hasher(), const key_equal equal key_equal(), const allocator_type alloc allocator_type() ) : concurrent_unordered_base(init.begin(), init.end(), bucket_count, hash, equal, alloc) {}另外C17 起头文件还提供了类模板参数推导指南deduction guides使concurrent_unordered_set s{1, 2, 3};这类省略模板参数的写法能正确推导类型见 concurrent_unordered_set.h 推导指南这为初始化列表构造提供了额外的类型推导保障。拷贝构造器concurrent_unordered_set( const concurrent_unordered_set other ); concurrent_unordered_set( const concurrent_unordered_set other, const allocator_type alloc );语义构造other的副本。未提供alloc时新容器的分配器由std::allocator_traitsallocator_type::select_on_container_copy_construction(other.get_allocator())获得。若拷贝过程中other正被并发操作行为未定义UB。从 基类拷贝构造实现 看拷贝过程复制了my_size、my_bucket_count、my_max_load_factor、my_hash_compare与段表my_segments随后通过internal_copy(other)深拷贝全部元素并配有异常保护——一旦拷贝中途抛出异常会执行clear()保证不泄漏concurrent_unordered_base( const concurrent_unordered_base other, const allocator_type alloc ) : my_size(other.my_size.load(std::memory_order_relaxed)), my_bucket_count(other.my_bucket_count.load(std::memory_order_relaxed)), my_max_load_factor(other.my_max_load_factor), my_hash_compare(other.my_hash_compare), my_head(other.my_head.order_key()), my_segments(other.my_segments, alloc) { try_call( [] { internal_copy(other); } ).on_exception( [] { clear(); }); }在 concurrent_unordered_set.h 中带分配器的拷贝构造器正是把other和alloc直接委托给基类完成。因此“快照式”拷贝要求调用方自行保证other不被其他线程同时读写——这是使用拷贝构造构造线程间“视图快照”时的关键前提。移动构造器concurrent_unordered_set( concurrent_unordered_set other ); concurrent_unordered_set( concurrent_unordered_set other, const allocator_type alloc );语义以移动语义构造出含有other内容的容器other被置于**有效但未指定valid but unspecified**的状态未提供alloc时分配器由std::move(other.get_allocator())获得。与拷贝构造一样other若正被并发操作则行为未定义。基类移动构造实现 分两种路径concurrent_unordered_base( concurrent_unordered_base other ) : /* 各成员以 relaxed 语义从 other 装载 */ { move_content(std::move(other)); // 直接接管内容避免逐元素移动 } concurrent_unordered_base( concurrent_unordered_base other, const allocator_type alloc ) : /* ... */ { using is_always_equal typename allocator_traits_type::is_always_equal; internal_move_construct_with_allocator(std::move(other), alloc, is_always_equal()); }也就是说不带分配器的移动构造走轻量的move_content指针级接管而带分配器的版本还需比较两个分配器的可交换性propagate_on_container_move_construction/is_always_equal必要时退化为逐元素移动——这解释了为什么实践中优先使用默认移动构造来转移大容器。析构函数~concurrent_unordered_set();语义销毁容器逐一调用存储元素的析构函数并释放全部已用内存。若析构时*this正被并发操作行为未定义。基类中的实现只有一行见 destructor~concurrent_unordered_base() { internal_clear(); }internal_clear()负责按桶遍历并释放每个节点、销毁元素。工程上需要注意多线程场景中必须在所有线程完成对该容器的访问join 屏障之后才允许容器对象离开作用域否则触发 UB。赋值运算符拷贝赋值concurrent_unordered_set operator( const concurrent_unordered_set other );语义用other的元素副本替换*this的全部元素当std::allocator_traitsallocator_type::propagate_on_container_copy_assignment::value为true时分配器也随之拷贝。若*this或other正被并发操作行为未定义。返回值*this的引用。对应 基类实现 为“清空 拷贝元数据 internal_copy”并带有自赋值检查if (this ! other)concurrent_unordered_base operator( const concurrent_unordered_base other ) { if (this ! other) { clear(); my_size.store(other.my_size.load(std::memory_order_relaxed), std::memory_order_relaxed); my_bucket_count.store(other.my_bucket_count.load(std::memory_order_relaxed), std::memory_order_relaxed); my_max_load_factor other.my_max_load_factor; my_hash_compare other.my_hash_compare; my_segments other.my_segments; internal_copy(other); } return *this; }注意它是先clear()再拷贝的实现策略赋值成功后*this的内容与other一致但在拷贝进行期间容器状态并不是一致快照且该操作非线程安全只能在独占访问窗口内调用。移动赋值concurrent_unordered_set operator( concurrent_unordered_set other ) noexcept(/*See below*/);语义以移动语义用other的元素替换*this的全部元素other留下有效但未指定的状态当propagate_on_container_move_assignment::value为true时分配器被移动。*this与other任一侧正被并发操作则行为未定义。返回值*this的引用。规范给出的noexcept说明为noexcept(std::allocator_traitsallocator_type::is_always_equal::value std::is_nothrow_move_assignablehasher::value std::is_nothrow_move_assignablekey_equal::value)即分配器恒等比较、hasher与key_equal的移动赋值均不可抛异常时整个移动赋值才承诺不抛异常。基类实现源码的noexcept依据是unordered_segment_table::is_noexcept_assignment并借助propagate_on_container_move_assignment与is_always_equal的析取决定分配器传播路径与规范语义一致。初始化列表赋值concurrent_unordered_set operator( std::initializer_listvalue_type init );语义用init中的元素替换*this的全部元素若init中存在多个相等元素保留哪个未指定。若*this正被并发操作行为未定义。返回值*this的引用。在 concurrent_unordered_set.h 中它是一个转发到基类operator(initializer_list)的薄封装concurrent_unordered_set operator( std::initializer_listvalue_type il ) { base_type::operator (il); return *this; }因此支持set {1, 2, 3};这种整体重置写法适合在单线程阶段重新初始化容器。使用要点与测试依据汇总初始化选型需要预设容量时用bucket_count构造会被向上取整为 2 的幂从现有数据批量装载用区间构造字面量场景用初始化列表构造/赋值容器间转移优先用移动构造/移动赋值避免逐元素开销。线程安全边界本节约束的核心是——构造区间/拷贝/移动、析构、赋值全部属于非并发安全操作规范要求对源/目标容器“无并发操作”UB 条款。并发阶段只应使用insert、emplace、find、count、contains等规范标注为 concurrently safe 的接口删除必须走单线程窗口内的unsafe_erase系列。实现一致性验证仓库中 TBB 自带功能测试 test_concurrent_unordered_set.cpp 与标准符合性测试 conformance_concurrent_unordered_set.cpp覆盖了这些构造/赋值重载的行为验证在 mold 仓库中TBB 作为第三方库随源码分发third-party/tbb/链接器本体主要使用其中的parallel_for_each、concurrent_vector等组件理解本容器的生命周期语义对扩展使用 TBB 并发容器同样适用。默认参数缺省分配器为tbb::tbb_allocatorKey见头文件模板默认参数初始桶数为 8、初始max_load_factor为 4.0均可通过构造参数或后续max_load_factor接口调整。综上规范文档 construction_destruction_copying.rst 给出了concurrent_unordered_set生命周期接口的完整契约而third-party/tbb/include/oneapi/tbb/detail/_concurrent_unordered_base.h中的实现进一步揭示了默认桶数、桶数取整、拷贝的异常保护与移动的内容接管策略——两者对照阅读是正确使用该并发容器的可靠基础。【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表