TBB同线程任务切换下enumerable_thread_specific有效性及解决方案咨询
问题核心结论
你所担心的「同一线程上任务A中途暂停切换到任务B」的场景,在你当前描述的代码前提下不会发生。
TBB采用协作式任务调度机制,而非操作系统级别的抢占式调度:只有当任务内部主动触发调度点(包括嵌套调用TBB并行算法、调用tbb::yield、等待任务组完成、调用TBB同步原语等)时,线程才会暂停当前任务,窃取执行其他就绪任务。你已经明确BFS逻辑中没有其他parallel_for调用,也没有其他调度触发操作,因此每个任务都会在分配到的线程上完整执行完毕,再执行下一个任务,共享同一份TLS本身不会出现执行过程中的数据冲突。
注意:哪怕不存在任务切换问题,你当前的代码也需要在拿到
traversedVisited之后先调用clear()清空上一个同线程任务留下的残留数据,否则BFS逻辑会引入脏数据错误。
存在调度点场景的解决方案
如果后续代码迭代需要在任务逻辑中加入调度点,可根据场景选择以下方案解决TLS污染问题:
- 方案1:改用任务内局部变量
直接把遍历用的std::set<int>定义在parallel_for的lambda内部,每个任务独享一份实例,完全不存在共享冲突。优点是实现最简单,逻辑无歧义;缺点是如果任务数量非常多,会产生大量set初始化、销毁的开销,适合任务量级不大的场景。
代码示例:tbb::parallel_for(0, n, [&] (int i) { std::set<int> traversedVisited; // 执行BFS操作 }); - 方案2:搭配
task_isolation保证TLS安全
如果需要保留TLS复用内存的优势,避免频繁创建销毁set的开销,可以用TBB的任务隔离机制把任务执行逻辑包裹起来,哪怕内部有调度点,也会保证当前任务执行完成前,线程不会执行其他无关任务,TLS的数据不会被其他任务污染。
代码示例:tbb::enumerable_thread_specific<std::set<int>> tls; tbb::parallel_for(0, n, [&] (int i) { tbb::this_task_arena::isolate([&] { auto &traversedVisited = tls.local(); traversedVisited.clear(); // 使用traversedVisited执行BFS操作 }); }); - 方案3:使用对象池复用实例
如果既需要复用实例降低开销,又不想受隔离机制的限制,可以实现一个轻量的set对象池,每个任务执行时从池中取一个清空的set实例,用完后归还到池即可,仅需要在池的取/还操作加轻量锁,开销远低于每次新建set。
内容的提问来源于stack exchange,提问作者user3677630
相关产品推荐
相关产品推荐

