 的语义、实现与正确使用)
并发编程高性能计算【免费下载链接】oneTBBoneAPI Threading Building Blocks (oneTBB)项目地址https://gitcode.com/gh_mirrors/on/oneTBB点击查看免费下载enumerable_thread_specificETS是 oneAPI Threading Building BlocksoneTBB提供的线程局部存储TLS类模板而Concurrently unsafe modifiers并发不安全修改器则专门规定了 ETS 中必须串行执行的修改操作。本篇指南以 unsafe_modifiers.rst 为骨架结合 oneTBB 头文件实现与测试用例系统讲解clear()的语义边界、底层实现、与并发安全操作如local()的配合方式以及在实际并行程序中的正确用法帮助读者避免数据竞争、写出可验证的正确代码。一、定位ETS 成员函数的两类并发属性在深入clear()之前有必要先明确 ETS 的成员函数分类。oneTBB 参考文档将enumerable_thread_specific的成员函数划分为两类并发安全修改器Concurrently safe modifierslocal()与local(bool exists)它们可以与其他并发安全方法并发执行是 ETS 中线程访问自己局部副本的唯一入口见 safe_modifiers.rst并发不安全修改器Concurrently unsafe modifiers即本节主题clear()它只能串行执行。原文档明确给出总则见 unsafe_modifiers.rstAll member functions in this section can only be performed serially. The behavior is undefined in case of concurrent execution of these methods with other (either concurrently safe) methods.翻译过来即本节所有成员函数只能串行执行若这些方法与其它即使是并发安全的方法并发执行行为是未定义的UB。也就是说clear()不仅要避开其它clear()还必须避开local()、迭代器遍历、size()等一切其它操作——这是理解本节语义的第一原则。为什么未定义行为而非线程安全保证从实现角度可以解释这一点。ETS 的线程局部元素存放在一个tbb::concurrent_vector中而clear()会一次性销毁其中全部元素。查看 enumerable_thread_specific.h 的实现//! Destroys local copies void clear() { my_locals.clear(); this-table_clear(); // callback is not destroyed }其中my_locals的类型是internal_collection_type即tbb::concurrent_vectorpadded_element, padded_allocator_type见同文件第 791 行。clear()同时执行了两步清理my_locals.clear()销毁concurrent_vector中已创建的全部线程局部元素table_clear()清空底层的线程 ID → 元素指针的哈希映射表ets_base类维护使得后续local()调用重新走创建新元素路径。虽然concurrent_vector::clear()本身对其它clear()/grow_by有一定并发保护但table_clear()与local()中table_lookup()的读写会构成竞争且销毁元素与其他线程正持有对该元素的引用无法安全共存。因此标准并未承诺任何并发安全性只承诺在串行前提下语义正确——这也是文档将其明确定义为行为未定义的根源。二、clear()的精确语义原文档对clear()的规范描述如下void clear();Destroys all elements in*this.即销毁*this中的全部元素。需要精确理解销毁的边界销毁的是所有已创建的线程局部元素无论它们属于哪个线程元素对应的T对象析构函数会被调用通过concurrent_vector的元素销毁机制元素计数归零size()随后返回 0empty()返回true构造回调construct callback不会被销毁注释// callback is not destroyed表明ETS 保存的如何创建新元素的构造器默认构造、exemplar 拷贝、finit()求值或T(args...)构造在clear()之后仍然有效。这意味着clear()之后线程再次调用local()时会按照最初构造 ETS 时指定的规则重新创建全新元素而非变成空元素或未定义状态。clear()之后的元素重建语义这一重建行为与 ETS 的惰性创建模型一致。ETS 的核心设计是线程局部元素是惰性创建的——新建的 ETS 没有任何元素当某个线程第一次调用local()时才创建属于它的元素元素个数等于访问过该 ETS 的不同线程数而非应用中实际的线程总数见 enumerable_thread_specific_cls.rst 的类总述。clear()相当于把 ETS 重置到刚构造完、尚未有任何线程访问的状态因此后续local()会再次按构造时的规则创建元素。三、与 ETS 整体成员函数的配合关系要把clear()用对必须把它放在 ETS 完整成员函数体系中理解。ETS 的全部成员函数及其并发属性可归纳如下对应 enumerable_thread_specific_cls.rst 与各子章节文档类别成员函数并发属性说明构造默认构造、Finit、exemplar、变参构造构造时见 construct_destroy_copy.rst拷贝/移动拷贝/移动构造与赋值构造/赋值时需在无并发访问时执行并发安全修改器local()、local(bool)可并发见 safe_modifiers.rst并发不安全修改器clear()必须串行本篇文章主题大小与容量size()、empty()只读查询见 size_and_capacity.rst迭代begin()/end()/range(grainsize)遍历见 iteration.rst归约combine(f)、combine_each(f)串行遍历见 combining.rst一个典型的 ETS 生命周期大致是构造阶段选择构造方式默认构造、exemplar、finit()或T(args...)并可选指定分配器与ETS_key_type底层实现并行填充阶段各线程通过local()访问并修改自己的副本可并发归约/消费阶段调用combine()/combine_each()汇总或通过range()交给parallel_for并行归约此时遍历与其它访问需同步重置阶段调用clear()清空全部元素使 ETS 恢复空容器状态可复用进入下一轮填充。关键约束是阶段 4 必须在没有任何其它线程正在调用该 ETS 的任何方法时执行即串行地、独占地调用。四、源码级验证实现与测试中的clear()1. 头文件实现clear()的完整实现位于 enumerable_thread_specific.h。其中my_locals是tbb::concurrent_vector第 795 行table_clear()来自ets_baseETS_key_type基类第 107 行起负责清空线程标识到局部元素地址的映射表。值得注意的是ETS 底层使用了ets_key_selectorETS_key_typeenumerable_thread_specific.h默认的ets_no_key以std::this_thread::get_id()作为线程标识不消耗任何原生 TLS 键ets_key_per_instance每个 ETS 实例消耗 1 个原生 TLS 键数量可能有限ets_suspend_aware在启用了可恢复任务__TBB_RESUMABLE_TASKS时可用以suspend_point为键规避task::suspend导致线程身份变化的问题详见 enumerable_thread_specific_cls.rst 的枚举常量说明。无论选择哪种底层实现clear()都会同时清空元素容器与映射表因此重置语义对三种实现一致。2. 测试用例佐证oneTBB 的测试为clear()的销毁后可按原规则重建语义提供了直接验证在 conformance_enumerable_thread_specific.cpp 的ets_copy_assign_test中先通过拷贝构造/赋值得到新 ETS随后调用copy.clear()、assign.clear()再调用copy.local(existed)并断言existed falseREQUIRE_MESSAGE(existed, ...)证明clear()之后元素确实被销毁下一次local()会重新创建全新元素同文件第 874 行check(check_alignment(copy.local(),allocator_name))还验证了重建元素的对齐与分配器行为依然正确在 test_enumerable_thread_specific.cpp 中多线程场景下会通过a[i].clear()在串行区间重置各 ETS用于隔离不同测试轮次的状态避免残留的线程局部数据污染后续断言。这些测试的共性模式是clear()与local()之间必须存在明确的串行点例如在单线程测试函数中依次执行或用栅栏/等待保证所有工作线程已退出这正好印证了文档对只能串行执行的强制要求。五、实战指南何时用、怎么用、避免哪些坑1. 典型使用场景clear()最自然的应用是复用 ETS 容器进行多轮并行计算。例如#include oneapi/tbb/enumerable_thread_specific.h #include oneapi/tbb/parallel_for.h #include oneapi/tbb/blocked_range.h #include vector using namespace oneapi::tbb; // 每一轮计算都独立累加轮与轮之间需要清零 enumerable_thread_specificlong long ets; // 默认构造各线程局部值默认初始化为 0 for (int round 0; round 10; round) { // 阶段 A并行填充并发安全 parallel_for(blocked_rangestd::size_t(0, data.size()), { long long local ets.local(); for (std::size_t i r.begin(); i r.end(); i) local data[i]; }); // 阶段 B串行归约此时所有工作线程应已完成该轮 long long sum ets.combine(std::pluslong long()); results.push_back(sum); // 阶段 C串行重置为下一轮做准备 ets.clear(); }这里的纪律是clear()必须发生在阶段 B 全部完成、且没有任何线程仍持有local()引用之后。若parallel_for已同步返回通常可认为满足条件但若 ETS 被多个独立任务或外部线程共享则必须在调用clear()前通过等待/栅栏确保访问者全部退出。2. 必须避免的并发陷阱陷阱一clear()与local()并发。若线程 A 正在ets.local()上写入线程 B 同时ets.clear()则 A 可能在元素被销毁后仍使用悬垂引用或在销毁过程中写入——这正是文档定义的未定义行为。concurrent_vector的内部保护无法覆盖销毁被引用元素这一场景。陷阱二clear()与迭代/归约并发。combine()、combine_each()以及begin()/end()遍历在调用线程中串行扫描全部元素见 combining.rst若与clear()并发迭代器会越过被销毁的元素造成未定义行为。陷阱三误以为clear()之后元素仍存在。clear()不是置零而是彻底销毁。之后local(exists)返回的exists false且新元素按构造规则重新创建可能是重新默认构造、重新拷贝 exemplar、或重新执行finit()。如果每轮计算依赖上一次的中间值clear()会破坏该依赖。陷阱四忽略线程标识复用的影响。文档还提醒见 enumerable_thread_specific_cls.rst 的 caution 段落ETS 使用std::this_thread::get_id()返回的 OS 相关值标识线程该值仅在线程存活期间保证唯一新线程可能获得与已销毁线程相同的 ID。因此元素个数可能少于实际访问线程数线程首次引用的元素也可能不是新建的。clear()能重置这一状态但无法消除 OS 层 ID 复用本身。3. 与其它串行化操作的边界clear()只负责元素与映射表不销毁 ETS 自身。ETS 的析构函数enumerable_thread_specific.h会销毁构造回调、并在free_array()失效前清空哈希表。如果需要彻底释放 ETS 实例占用的全部资源包括构造回调与原生 TLS 键应让 ETS 对象离开作用域析构而不是依赖clear()。六、小结要点结论并发属性clear()属于并发不安全修改器只能串行执行与任何其它方法并发均为未定义行为语义销毁*this中全部线程局部元素元素计数归零构造回调保留重建行为clear()后local()重新按原构造规则创建全新元素local(exists)返回exists false底层实现my_locals.clear()清空concurrent_vectortable_clear()清空线程标识映射表见 enumerable_thread_specific.h适用场景多轮并行计算的轮间重置测试中的状态隔离见 conformance_enumerable_thread_specific.cpp主要风险与local()/迭代/归约并发导致悬垂引用与 UB误以为clear()是置零而非销毁clear()是 ETS 容器模型中唯一面向整体重置的修改操作。它的正确性完全建立在串行独占调用这一纪律之上只要在调用clear()前确保没有其它线程正在访问该 ETS 的任何成员函数它就能可靠地把 ETS 恢复到空容器、可复用的初始状态是构建多轮并行累加、滑动窗口类算法时不可或缺的工具。赞分享并发编程高性能计算【免费下载链接】oneTBBoneAPI Threading Building Blocks (oneTBB)项目地址https://gitcode.com/gh_mirrors/on/oneTBB点击查看免费下载相关推荐oneTBB concurrent_priority_queue 的非并发安全修改器clear 与 swap 的语义、实现原理与正确用法oneTBB concurrent_priority_queue 的非并发安全修改器clear 与 swap 的语义、实现原理与正确用法 导读 本文聚焦 on并发编程高性能计算mold 内置 oneTBB 线程本地存储深入enumerable_thread_specific 的并发不安全修改器 clear() 语义与源码解析mold 内置 oneTBB 线程本地存储深入enumerable_thread_specific 的并发不安全修改器 clear 语义与源码解析 enume开发工具构建工具系统编程Reach UI Listbox 设计内幕可组合性、受控状态与 SSR 的正确打开方式Reach UI Listbox 设计内幕可组合性、受控状态与 SSR 的正确打开方式 reach/listbox 是 Reach UI 中用于构建无障碍自前端UI组件上一篇OpenCore Legacy Patcher 完整指南2008—2017 老款 Mac 一步步装上最新 macOS下一篇从登录到仪表盘Argon Design System React实战案例详解创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考