据我所知,tbb可能会维护池线程以供重用。。。有没有一种方法可以确保我使用现代C++实现声明为thread_local
的数据,以及一个非平凡(默认(构造函数和析构函数,当tbb将一个线程放入其池中时,它会被销毁,而当线程从池中取出时,它又会被重新构造?正如我所说,我目前只是将数据声明为静态,并使用C++thread_local
说明符。
编辑:
很抱歉最初没有拼写出来,但一位早期的受访者明确表示,可能对我希望更新的代码做出了一些假设,但这些假设是无效的。
-
重构tbb的使用是不现实的,因为代码已经大量使用了它,并且重构它们是不容易的,但我需要它创建的线程仍然可以访问线程本地数据。
-
A将对thread_local数据的所有访问隐藏在少数函数后面,这正是我理想中希望更改的。有没有某种方法,也许通过一个额外的thread_local值,我可以判断我所在的线程自上次访问数据以来一直被重用?这实际上是我正在寻找的理想解决方案。
-
我发现重构应用程序中所有tbb调用的一个主要缺点不是它们太多(尽管这肯定是一个重要因素(,而是在每个tbb线程中添加对thread_local数据的引用,即使那个特定的线程实际上并不需要访问它。在延迟构造thread_local数据直到第一次访问它的系统上,这种开销是不可取的。这就是为什么理想情况下,我希望将它的逻辑放在访问thread_local数据的函数中。
程序逻辑上下文和线程应该被视为另一个概念。线程由程序上下文使用。您的程序上下文可能需要一个与程序上下文相互创建和销毁的线程,或者可能在池线程提供的线程上运行,或者可能运行在按需创建或从线程池提供的多个线程上。
- 一个线程
您的程序上下文只需要使用线程本地实例。
- 池线程
您的程序上下文需要重新初始化/创建一个线程本地实例,因为该线程本地实例已经被另一个上下文的线程使用,并且它与您当前的程序上下文无关。
- 多线程
如果程序上下文由多个线程组成(例如,在线程t1上运行的func a((,在线程t2上运行的func b(((,则不应使用线程本地存储,因为程序上下文无法确保数据的完整性。
在下面的代码中,TBB将池线程中的一个线程分配给一个单独的parallel_fo上下文,因此,除非您首先在上下文中初始化线程本地实例,否则此计数上下文的数据将被折叠。
#include <ranges>
#include <tbb/tbb.h>
using namespace std;
using namespace std::ranges::views;
int main() {
class A{
public:
A(int ctx) : m_ctx(ctx){}
int m_ctx;
int m_num = 0;
};
thread_local A a(-1); // already existing instance
tbb::parallel_for(0, 7, 1, [](int i) {
//a = A(i); // re-initialize thread local
a.m_ctx = i; // not re-initize thread local, only assign context id
for (auto i : iota(0, 5)) {
a.m_num++;
printf("ctx=%d count=%d th=%d n", a.m_ctx, a.m_num, tbb::this_task_arena::current_thread_index());
}
});
}
未重新初始化线程本地实例的输出。因此,计数值被折叠,因为TBB为另一个程序上下文分配了一个已经使用线程本地实例的线程。
没有重新初始化的输出被折叠。
ctx=0 count=1 th=0
ctx=0 count=2 th=0
ctx=0 count=3 th=0
ctx=0 count=4 th=0
ctx=0 count=5 th=0
ctx=1 count=6 th=0
ctx=1 count=7 th=0
ctx=1 count=8 th=0
ctx=1 count=9 th=0
ctx=1 count=10 th=0
ctx=2 count=11 th=0
ctx=2 count=12 th=0
ctx=2 count=13 th=0
ctx=3 count=1 th=1
ctx=3 count=2 th=1
ctx=3 count=3 th=1
ctx=3 count=4 th=1
ctx=3 count=5 th=1
ctx=5 count=1 th=2
ctx=5 count=2 th=2
ctx=5 count=3 th=2
ctx=5 count=4 th=2
ctx=5 count=5 th=2
ctx=6 count=1 th=4
ctx=6 count=2 th=4
ctx=6 count=3 th=4
ctx=6 count=4 th=4
ctx=6 count=5 th=4
ctx=2 count=14 th=0
ctx=2 count=15 th=0
ctx=4 count=1 th=3
ctx=4 count=2 th=3
ctx=4 count=3 th=3
ctx=4 count=4 th=3
ctx=4 count=5 th=3
重新初始化线程本地实例的输出。它运行良好。
ctx=0 count=1 th=0
ctx=0 count=2 th=0
ctx=0 count=3 th=0
ctx=0 count=4 th=0
ctx=0 count=5 th=0
ctx=1 count=1 th=0
ctx=1 count=2 th=0
ctx=1 count=3 th=0
ctx=1 count=4 th=0
ctx=1 count=5 th=0
ctx=2 count=1 th=0
ctx=2 count=2 th=0
ctx=2 count=3 th=0
ctx=2 count=4 th=0
ctx=2 count=5 th=0
ctx=5 count=1 th=2
ctx=5 count=2 th=2
ctx=5 count=3 th=2
ctx=5 count=4 th=2
ctx=5 count=5 th=2
ctx=6 count=1 th=0
ctx=6 count=2 th=0
ctx=6 count=3 th=0
ctx=6 count=4 th=0
ctx=6 count=5 th=0
ctx=4 count=1 th=3
ctx=4 count=2 th=3
ctx=4 count=3 th=3
ctx=4 count=4 th=3
ctx=4 count=5 th=3
ctx=3 count=1 th=1
ctx=3 count=2 th=1
ctx=3 count=3 th=1
ctx=3 count=4 th=1
ctx=3 count=5 th=1
我不确定这是否是您想要的,但您可以使用以下代码来检查线程是否已回收:
Code: Select all #include <tbb/tbb.h>
#include <iostream>
int main() {
tbb::task_scheduler_init init;
tbb::parallel_for(0, 10, 1, [](int i) {
std::cout << "Thread " << tbb::this_task_arena::current_thread_index() << " is running" << std::endl;
});
tbb::parallel_for(0, 10, 1, [](int i) {
std::cout << "Thread " << tbb::this_task_arena::current_thread_index() << " is running" << std::endl;
});
return 0;
}
Code: Select all Thread 0 is running
Thread 1 is running
Thread 2 is running
Thread 3 is running
Thread 4 is running
Thread 5 is running
Thread 6 is running
Thread 7 is running
Thread 8 is running
Thread 9 is running