- 并发编程
- 高性能计算
【免费下载链接】oneTBB
oneAPI Threading Building Blocks (oneTBB)
this_task_arena是 oneTBB 提供的一个命名空间,用于让调用线程与"当前正在使用的 task_arena"(任务竞技场)进行交互,包含查询当前线程在竞技场中的索引、查询竞技场并发度、以隔离方式执行函数、向竞技场异步投递任务等全局函数。本文以 this_task_arena_ns.rst 为骨架,结合 task_arena.h 头文件实现、arena.cpp 运行时实现与测试用例,系统讲解该命名空间的每个 API 的语义、注意事项与底层原理,帮助你准确地在自己的并行程序中使用"当前竞技场"的能力。
背景:什么是"当前 task_arena"
在 oneTBB 中,task_arena(详见 task_arena_cls.rst)代表一个显式的、由用户管理的任务调度竞技场,线程在其中共享并执行任务;竞技场的并发度(concurrency level)限制了可同时执行任务的线程数量。每个用户线程如果在显式task_arena之外调用任何并行构造,都会使用与该调用线程关联的隐式竞技场(implicit task arena)表示对象。
this_task_arena命名空间正是为"调用线程当前所在的竞技场"这一概念提供全局函数入口——无论该竞技场是显式创建并通过execute加入的,还是线程默认关联的隐式竞技场,都能通过本命名空间的函数统一操作。从实现上看,这些函数在 task_arena.h 中通过using声明汇集了detail::d1命名空间中的内部实现,并以nullptr作为"当前竞技场"的标记传给运行时:
namespace this_task_arena { using detail::d1::current_thread_index; using detail::d1::max_concurrency; using detail::d1::isolate; using detail::d1::enqueue; using detail::d1::start_parallel_phase; using detail::d1::end_parallel_phase; } // namespace this_task_arenaAPI 总览
this_task_arena定义在头文件<oneapi/tbb/task_arena.h>中,完整接口如下(与 this_task_arena_ns.rst 中的声明一致):
namespace oneapi { namespace tbb { namespace this_task_arena { int current_thread_index(); int max_concurrency(); template<typename F> auto isolate(F&& f) -> decltype(f()); template<typename F> void enqueue(F&& f); template<typename F> void enqueue(F&& f, task_group& tg); void enqueue(task_handle&& h); // Preview feature: parallel_phase Interface void start_parallel_phase(); void end_parallel_phase(bool with_fast_leave = false); } // namespace this_task_arena } // namespace tbb } // namespace oneapi从功能上可分为四组:
- 查询函数:
current_thread_index()、max_concurrency(),用于获知当前线程与竞技场的运行时状态; - 隔离执行:
isolate(),将一段代码限制为只处理其自身范围内调度的任务; - 异步投递:三个
enqueue()重载,向当前竞技场投递任务后立即返回; - 预览特性:
start_parallel_phase()/end_parallel_phase(),向调度器提示并行区域的起止(需宏TBB_PREVIEW_PARALLEL_PHASE开启)。
查询当前线程的竞技场索引:current_thread_index()
int current_thread_index();该函数返回调用线程在当前task_arena中的线程索引(thread index)。线程索引是一个介于0 与竞技场并发度之间的整数;如果调用线程尚未初始化任务调度器,则返回task_arena::not_initialized(其值为-2,见 task_arena.h)。
语义要点
- 索引在应用线程(application thread)和工作者线程(worker thread)加入竞技场时分配,直到退出竞技场时保持;
- 同一竞技场内的线程索引唯一——同一时刻竞技场中不会有两个线程持有相同索引,但索引不保证连续(例如线程退出后空出的槽位可能不立即复用);
- 底层实现通过
r1::execution_slot(nullptr)查询当前线程的槽位号,若返回slot_id(-1)则映射为not_initialized(见 task_arena.h)。
三个重要注意事项
- 索引可能在任务之间变化:由于线程若未执行任务可随时退出竞技场,即使属于同一 task_group 或同一算法的两个任务之间,线程索引也可能改变;
- 不同竞技场的索引可相同:使用不同竞技场的线程可能持有相同的索引值,因此索引只在"同一竞技场内部"具有唯一性语义;
- 嵌套竞技场会临时改变索引:在
execute()中加入嵌套竞技场时,当前索引值会改变,但外层竞技场的索引会在返回时恢复。
典型用法与测试佐证
current_thread_index()常用于并行分区、负载均衡或调试时识别执行线程。例如在 test_arena_constraints.cpp 中,测试通过它验证嵌套竞技场的行为:
if (tbb::this_task_arena::current_thread_index() > 0) { // ... } REQUIRE_MESSAGE(tbb::this_task_arena::max_concurrency() == 1, "Nested arena should have 1 slot.");在 test_partitioner.cpp 中,partitioner 测试使用current_thread_index()记录每个执行线程的槽位:
int thread_id = tbb::this_task_arena::current_thread_index();查询当前竞技场的并发度:max_concurrency()
int max_concurrency();返回调用线程当前task_arena的并发度(concurrency level),即竞技场内可同时参与任务处理的线程数上限。如果调用线程尚未初始化任务调度器,则返回根据硬件配置自动确定的并发度——从实现看,它直接委托给r1::max_concurrency(nullptr)(见 task_arena.h)。
与task_arena::max_concurrency()的差异
类方法task_arena::max_concurrency()返回的是该task_arena对象(无论是否初始化)配置的并发度;而this_task_arena::max_concurrency()是"当前竞技场"的并发度,适用于不持有竞技场对象句柄、或只想了解当前执行环境的场景。两者底层最终都汇聚到运行时层对竞技场槽位数量的查询逻辑。
使用场景
- 在并行算法内部获取当前竞技场的线程数,用于自适应分块;
- 在进入显式竞技场之前查询系统默认并发度(此时等价于硬件并发度);
- 测试中验证嵌套竞技场并发度被正确约束,如 test_flow_graph.cpp 断言
max_concurrency() == 1。
隔离执行:isolate()
template<typename F> auto isolate(F&& f) -> decltype(f());在隔离区域(isolation region)中运行指定的函数对象(functor):调用线程被限制为只处理该函数作用域内调度的任务,并返回函数对象的返回值。F类型必须满足 ISO C++ 标准 [function.objects] 一节描述的 Function Objects 要求。
核心语义
isolate的典型用途是串行化关键区段:当多个线程在同一个竞技场中并行执行时,如果某段代码不希望与其他线程的任务发生交叉(例如必须独占某些资源或保证执行顺序),可用isolate包裹;- 从实现看,
isolate最终调用r1::isolate_within_arena(func, /*isolation*/ 0)(见 task_arena.h),把函数对象包装为task_arena_function委托后送入运行时执行; - 运行时保证:在隔离区域内,调度器不会把其他(隔离区外)任务派给该线程执行。
一个重要告诫
函数对象返回的对象不能是引用类型。如果需要返回"引用语义"的结果,请改用std::reference_wrapper包装。
测试佐证
test_flow_graph_priorities.cpp 展示了isolate与流图配合的用法:
tbb::this_task_arena::isolate( IsolationFunctor(work_size) );异步投递任务:三个enqueue()重载
this_task_arena提供三种向"当前竞技场"投递任务的重载形式,投递后立即返回,任务由竞技场中的工作者线程(或额外的专用工作者线程)在合适时机执行。
形式一:投递函数对象
template<typename F> void enqueue(F&& f);将处理指定函数对象的任务投入调用线程当前使用的task_arena,然后立即返回。F必须满足 Function Objects 要求。其行为与对task_arena对象应用enqueue(F&& f)等价,只不过该task_arena是以attach参数构造(即连接到当前竞技场)的。
形式二:投递到指定 task_group
template<typename F> void enqueue(F&& f, task_group& tg);将处理函数对象的任务加入tg,并投递到调用线程当前使用的task_arena中。其行为严格等价于:
this_task_arena::enqueue( tg.defer(std::forward<F>(f)) );也就是说,tg.defer()先把函数对象封装成可延迟执行的任务,再通过enqueue(task_handle&&)重载入队。借助 task_group,调用方后续可以用tg.wait()等待该任务完成。
形式三:投递 task_handle
void enqueue(task_handle&& h);将h拥有的任务投递到调用线程当前使用的task_arena。行为与通用版本(template<typename F> void enqueue(F&& f))等价,只是参数类型不同。
注意:
h不能为空(empty),否则会导致未定义行为(undefined behavior)。实现中对应的断言位于 task_arena.h:__TBB_ASSERT(th != nullptr, "Attempt to schedule empty task_handle");。
底层实现
三个重载最终都汇入内部函数enqueue_impl(F&& f, task_arena_base* ta)(见 task_arena.h),其中ta传nullptr表示"当前竞技场":
template<typename F> void enqueue_impl(F&& f, task_arena_base* ta) { small_object_allocator alloc{}; r1::enqueue(*alloc.new_object<enqueue_task<typename std::decay<F>::type>>(std::forward<F>(f), alloc), ta); }enqueue_task(task_arena.h)继承自task,在execute中调用函数对象并自行释放内存。值得注意的是,入队任务采用small_object_allocator(小对象池分配器)管理生命周期,这也是 oneTBB 降低动态分配开销的惯用手法。
与task_arena::enqueue()的行为对照
类方法task_arena::enqueue()的语义(见 task_arena_cls.rst)对本命名空间同样适用,要点如下:
- 投递不要求调用线程加入竞技场——任意数量的外部线程都可以无阻塞地向竞技场提交工作;
- 即使总工作者线程数为零,也会创建一个额外的专用工作者线程来执行入队任务,保证任务最终会被执行;
- 不保证入队任务与竞技场中其他任务并发执行;
- 函数对象中抛出且未捕获的异常会导致未定义行为。
预览特性:parallel_phase接口
this_task_arena的预览特性parallel_phase接口(start_parallel_phase()/end_parallel_phase())用于向调度器提示并行区域的起止,从而影响工作者线程的驻留策略。完整的接口说明参见 parallel_phase.rst。
启用方式
这是一个预览特性(preview feature),需要定义宏:
#define TBB_PREVIEW_PARALLEL_PHASE 1启用后,特性测试宏TBB_HAS_PARALLEL_PHASE会被定义。注意预览特性可能在未来版本中发生不兼容变更。
设计动机
默认情况下,oneTBB 采用延迟线程退出(delayed thread leave)启发式:工作者线程完成竞技场中的工作后,会保留一段由实现定义的时长,预期很快会有新的并行任务到来,从而降低后续并行计算的启动延迟。这对大多数负载是有益的,但在以下场景可能适得其反:
- 并行任务以不规则的间隔提交、或存在长间隙,空闲线程浪费 CPU 资源;
- oneTBB 与其他线程库交错使用,空闲线程造成 CPU 过度订阅(oversubscription)。
parallel_phase接口让用户在周期性的并行工作区域前后打上标记:区域内调度器更积极地保留线程,区域结束后及时释放线程。
API 语义
void this_task_arena::start_parallel_phase(); void this_task_arena::end_parallel_phase(bool with_fast_leave = false);start_parallel_phase():标记当前竞技场中并行阶段的开始,作为调度器保留线程的提示;同时也可作为预热提示,允许调度器提前唤醒工作者线程;end_parallel_phase(bool with_fast_leave = false):标记并行阶段结束,调度器不再保留线程;若with_fast_leave为true,工作者线程的离开策略会被临时设置为fast。
对应用层更友好的 RAII 形式是task_arena::scoped_parallel_phase(文档中记为parallel_phase),构造时开始并行阶段、析构时结束,天然适配作用域(详见下文示例)。
源码级状态机
this_task_arena::start_parallel_phase/end_parallel_phase在 task_arena.h 中以nullptr为竞技场参数委托运行时:
inline void start_parallel_phase(task_arena::parallel_phase::flags f = {}) { r1::enter_parallel_phase(nullptr, static_cast<std::uintptr_t>(f.my_start_flags)); } inline void end_parallel_phase(task_arena::parallel_phase::flags f = {}) { r1::exit_parallel_phase(nullptr, static_cast<std::uintptr_t>(f.my_end_flags)); }运行时层(arena.cpp)将调用转发给竞技场内的线程离开管理器:
void task_arena_impl::enter_parallel_phase(d1::task_arena_base* ta, std::uintptr_t) { arena* a = ...; // 解析当前竞技场 a->my_thread_leave.register_parallel_phase(); } void task_arena_impl::exit_parallel_phase(d1::task_arena_base* ta, std::uintptr_t flags) { arena* a = ...; a->my_thread_leave.unregister_parallel_phase(flags); }thread_leave_manager(arena.h)用一个无锁状态字维护离开策略状态机,包含四种状态位:
static const std::uintptr_t DELAYED_LEAVE = 0; // 延迟离开(默认启发式) static const std::uintptr_t FAST_LEAVE = 1; // 快速离开 static const std::uintptr_t ONE_TIME_FAST_LEAVE = 1 << 1; // 一次性快速离开 static const std::uintptr_t PARALLEL_PHASE = 1 << 2; // 处于并行阶段register_parallel_phase()通过原子fetch_add(PARALLEL_PHASE)增加并行阶段计数(支持嵌套并行阶段),并清除可能存在的"一次性快速离开"状态;unregister_parallel_phase(flags)则递减计数,若这是最后一个并行阶段且携带end_fast_leave标志,则转入ONE_TIME_FAST_LEAVE,让线程在本次离开时采用快速策略(见 arena.h)。线程是否允许驻留由is_retention_allowed()判定:状态为FAST_LEAVE或ONE_TIME_FAST_LEAVE时不允许驻留。这套状态机正是文档中"保留策略"语义的底层实现。
离开策略(leave_policy)与 global_control 集成
task_arena::leave_policy枚举(automatic/fast)可在构造或initialize竞技场时设置;而global_control::leave_policy参数(见 global_control.h 与 parallel_phase.rst)提供应用级的默认控制。两者在竞技场创建时的组合规则如下:
竞技场leave_policy | 全局leave_policy | 初始状态 |
|---|---|---|
fast | 任意 | 快速离开 |
automatic | fast | 快速离开 |
automatic | automatic(默认) | 系统特定策略 |
要点:
- 多个
global_control对象并存时,只要任一存活对象的leave_policy为fast,全局生效值即为fast; - 已初始化的竞技场(含隐式竞技场)不受
global_control::leave_policy后续变更影响; - 全局控制提供应用级默认,
task_arena::leave_policy与parallel_phase提供竞技场级控制;竞技场初始化后,parallel_phaseAPI 可在运行时动态调整线程离开行为。
完整示例
来自 parallel_phase_example.cpp 的官方示例展示了global_control+scoped_parallel_phase的搭配用法:
#define TBB_PREVIEW_PARALLEL_PHASE 1 #include "oneapi/tbb/global_control.h" #include "oneapi/tbb/task_arena.h" #include "oneapi/tbb/parallel_for.h" #include "oneapi/tbb/parallel_sort.h" #include <vector> int main() { oneapi::tbb::global_control gc( oneapi::tbb::global_control::leave_policy, oneapi::tbb::task_arena::leave_policy::fast ); oneapi::tbb::task_arena ta; std::vector<int> data(1000); { oneapi::tbb::task_arena::parallel_phase phase{ta}; ta.execute([&data]() { oneapi::tbb::parallel_for(std::size_t(0), data.size(), &data { data[i] = static_cast<int>(i*i); }); }); for (std::size_t i = 1; i < data.size(); ++i) { data[i] += data[i-1]; } ta.execute([&data]() { oneapi::tbb::parallel_sort(data.begin(), data.end()); }); } }该例中,global_control::leave_policy被设为task_arena::leave_policy::fast,使得以automatic初始化的竞技场默认采用快速离开——并行工作一结束,工作者线程预期不再驻留。但工作流包含两段并行工作(初始化数据、排序)被串行工作(前缀和)隔开的模式,于是用scoped_parallel_phase(即task_arena::parallel_phaseRAII 对象)包裹整个区域,向调度器提示"后续还有并行工作",从而让工作者线程在两段并行工作之间得以驻留,避免频繁的线程唤醒/休眠开销。
使用建议与注意事项汇总
- 索引语义仅限同一竞技场内:
current_thread_index()返回的索引在竞技场内唯一但不保证连续,且可能随线程退出/重入而变化;跨竞技场比较索引没有意义。 not_initialized的判定:在调用任何并行构造之前查询索引会得到task_arena::not_initialized,代码中应显式处理该分支。isolate的返回值:不要从隔离函数中返回引用;需要引用语义时用std::reference_wrapper。- 入队任务的异常:
enqueue的函数对象中未捕获的异常会导致未定义行为,请务必在任务内自行捕获处理。 - 空的
task_handle:投递task_handle&&前必须确认其非空。 - 预览特性需显式开启:
parallel_phase接口依赖TBB_PREVIEW_PARALLEL_PHASE宏,并受TBB_HAS_PARALLEL_PHASE特性测试宏约束,生产环境使用前请评估 API 稳定性。
深入阅读
- task_arena 类参考:
task_arena的构造、execute/enqueue/wait_for等完整成员函数语义; - parallel_phase 接口文档:
leave_policy枚举、scoped_parallel_phase与global_control::leave_policy的完整说明; - task_arena.h:
this_task_arena命名空间声明与内部enqueue_impl、isolate_impl实现; - arena.cpp 与 arena.h:
enter_parallel_phase/exit_parallel_phase运行时转发与thread_leave_manager状态机; - test_arena_constraints.cpp:
current_thread_index/max_concurrency在嵌套竞技场场景下的测试佐证。
- 并发编程
- 高性能计算
【免费下载链接】oneTBB
oneAPI Threading Building Blocks (oneTBB)
相关推荐
oneTBB task_arena::attach 详解:用 attach 标签复用当前线程的任务竞技场
oneTBB task_arena::attach 详解:用 attach 标签复用当前线程的任务竞技场 本文是一份面向 oneAPI Threading Bu
并发编程高性能计算深入解析 oneTBB `this_task_arena` 命名空间:线程索引、并发度查询与隔离/入队调度原语
深入解析 oneTBB this_task_arena 命名空间:线程索引、并发度查询与隔离/入队调度原语 this_task_arena 是 oneTBB(当
开发工具构建工具系统编程oneTBB Task Scheduler Bypass 深入解析:让下一个任务直接在当前线程执行
oneTBB Task Scheduler Bypass 深入解析:让下一个任务直接在当前线程执行 导读 Task Scheduler Bypass(任务调度旁
开发工具构建工具系统编程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考