ThreadLocal如果Java 变量用作实例变量(例如,在生成线程局部对象的方法中),或者它们是否必须始终是静态的,那么Java 变量是否会产生线程局部值?
作为一个例子,假设一个典型的场景,其中初始化非线程安全的类的对象的几个昂贵,需要在单个静态初始化块中实例化,存储在单个类的静态变量中(例如,在Map数据中)结构)从那时起用于许多不同线程的密集处理.
为了实现线程安全,显然必须传递每个静态对象的不同副本.例如,DateFormat需要跨不同线程安全使用的Java 对象.
在网络上可以找到的许多示例中,方法似乎是分别声明每个ThreadLocal变量,在initialValue()方法中实例化新对象,然后使用该get()方法来检索线程局部实例.
如果要创建数十个或数百个这样的对象,每个都有自己的初始化参数,这种方法效率不高.例如,许多SimpleDateFormat对象各有不同的日期模式.
如果对象的实例化可以在循环中完成,该循环在每次迭代中产生不同的值,则在通过适当地初始化相应对象来创建每个值之后,将需要用于产生线程局部实例的通用方法.
基于以上所述,以下通用静态方法不起作用,因为每次调用initialValue()时都会产生相同的引用:
// Each value is an object initialized prior to calling getLocal(...)
public static final <T> T getLocal(final T value)
{
ThreadLocal<T> local = new ThreadLocal<T>()
{
@Override
protected T initialValue()
{
return value;
}
};
return local.get();
}
Run Code Online (Sandbox Code Playgroud)
相反,需要一种在initialValue()中创建新对象的机制.因此,唯一的通用方法可能是使用反射,类似于
private static final <T> T getLocal(
final Constructor<T> constructor, final Object[] initargs)
{
ThreadLocal<T> local …Run Code Online (Sandbox Code Playgroud) 我有一个大型的混合java/c ++应用程序,它将多个共享库加载到java中.它在我的Ubuntu 12.04 32位机器上工作正常,但我在使用Fedora 17 64位机器时遇到问题.一切都很好,但是当我尝试运行它时,我得到:
Exception in thread "main" java.lang.UnsatisfiedLinkError: /pathto/libmylib.so: dlopen: cannot load any more object with static TLS
Run Code Online (Sandbox Code Playgroud)
任何帮助是极大的赞赏!
更新:我已经将其缩小到与PCL(点云库)依赖关系的问题.libmylib.so依赖于PCL,但如果没有安装,也可以在没有PCL的情况下编译.没有PCL编译工作正常.我正在等待PCL社区的人们回复我,我会根据他们的建议更新这个帖子.
我发现MSDN中有关线程局部存储的初始值的矛盾. 这个页面说:
创建线程时,系统会为TLS分配一个LPVOID值数组,这些值初始化为NULL.
这让我相信,如果我用一个从未为同一索引调用过TlsSetValue的线程调用TlsGetValue,那么我应该得到一个空指针.
但是,这个页面说:
程序员应该确保......在调用TlsGetValue之前调用TlsSetValue.
这表明您不能依赖从TlsGetValue返回的值,除非您确定它已使用TlsSetValue显式初始化.
然而,第二页同时强调了初始化为零的行为:
存储在TLS槽中的数据的值可以为0,因为它仍然具有其初始值,或者因为线程称为TlsSetValue函数为0.
所以我有两个声明说数据被初始化为null(或0),并且有人说我必须在读取值之前显式初始化它.实验上,值似乎是自动初始化为空指针,但我无法知道我是否只是幸运,是否总是这样.
我试图避免使用DLL只是在DLL_THREAD_ATTACH上分配.我想按照以下方式进行懒惰分配:
LPVOID pMyData = ::TlsGetValue(g_index);
if (pMyData == nullptr) {
pMyData = /* some allocation and initialization*/;
// bail out if allocation or initialization failed
::TlsSetValue(g_index, pMyData);
}
DoSomethingWith(pMyData);
Run Code Online (Sandbox Code Playgroud)
这是一种可靠而安全的模式吗?或者,在尝试阅读之前,是否必须在每个线程中显式初始化插槽?
更新:文档还说TlsAlloc将已分配索引的插槽清空.因此,程序的另一部分之前是否已使用过一个插槽似乎无关紧要.
我在C++中使用协同程序的自定义实现(编译器g ++,在ARM上).协程可以通过调用move_to_thread函数(或其他方式,从一个线程迁移到另一个线程,但这将让我说明我的观点).我过于简单了,但有点像这样:
__thread int x = 0;
void f() {
x = 5;
// do some more work on current thread (thread 1, say)
move_to_thread(2);
// do more work, now on thread 2
int y = x; // with optimization, I'm getting the wrong x
}
Run Code Online (Sandbox Code Playgroud)
我遇到的问题是调用move_to_thread之前和之后完成的工作使用线程局部变量(使用__thread).在使用优化进行编译时,在线程2上运行的代码仍然访问线程1的线程局部变量而不是它自己的变量.这是因为对线程局部变量的访问执行以下操作:
但是,在启用优化的情况下,(1)和(2)正在针对第二次访问进行优化,因为编译器假定在特定线程上开始运行的函数将保留在该线程上.我的代码不适用这种假设.
如何在调用move_to_thread之前和之后让编译器查看正确的线程本地存储,而不完全取消优化?
我正在编写一个C++ 11类Foo,我想给每个实例提供自己的Bar类型的线程本地存储.也就是说,我希望每个线程和每个Foo实例分配一个Bar .
如果我使用pthreads,Foo将有一个类型为pthread_key_t的非静态成员,Foo的构造函数将使用pthread_key_create()进行初始化,而Foo的析构函数将使用pthread_key_delete()释放.或者,如果我只为Microsoft Windows编写,我可以使用TlsAlloc()和TlsFree()做类似的事情.或者如果我使用的是Boost.Thread,Foo会有一个类型为boost :: thread_specific_ptr的非静态成员.
但实际上,我正在尝试编写可移植的C++ 11.C++ 11的thread_local关键字不适用于非静态数据成员.因此,如果你想要每个线程一个Bar,那就没关系,但如果你想要每个Foo每个线程一个Bar,那就没关系.
所以据我所知,我需要定义一个从Foos到Bars的线程局部映射,然后处理每当Foo被销毁时如何正确清理的问题.但在我承诺之前,我在这里发帖是希望有人会阻止我并说"有一种更简单的方法."
(顺便说一句,我没有使用pthread_key_create()或boost :: thread_specific_ptr的原因是,如果我理解正确,他们会假设所有线程将分别使用pthreads或Boost.Thread生成.我不想做关于我的代码的用户将如何产生线程的任何假设.)
假设我有一次性类型:
class MyDisposable : IDisposable
{
public void Dispose() { }
}
Run Code Online (Sandbox Code Playgroud)
然后我希望每个线程都有自己的这个类的本地副本,每个线程一个:
private readonly ThreadLocal<MyDisposable> localMyDisposable
= new ThreadLocal<MyDisposable>(() => new MyDisposable());
Run Code Online (Sandbox Code Playgroud)
现在我想确保在线程退出之前,我调用Dispose()该值,如果它已创建.有没有办法做到这一点?我的应用程序中的一些线程是短暂的,有些线程很长.我想确保任何短命的人都能处理他们的价值观.
该ELF处理线程局部存储文件给出了各种模型装配顺序(本地的exec /初始EXEC /一般动态)的各种架构.但不是ARM - 我能在哪里看到ARM的代码序列吗?我正在编写一个编译器,并希望生成能够与平台链接器(程序和动态)一起正常运行的代码.
为清楚起见,我们假设一个ARMv7 CPU和一个非常新的内核和glibc(比如说3.13+/2.19+),但是如果这很容易解释,我也会对旧的hw/sw需要改变什么感兴趣.
该Thread.GetNamedDataSlot方法获取可以与被使用的时隙名称Thread.SetData.
可以GetNamedDataSlot缓存(并在所有线程中重用)函数的结果,还是应该在/为每个线程调用它?
文档没有明确说它"不应该"被重复使用,尽管它没有说它也可以.此外,该示例显示了在每个GetData/SetData站点使用的GetNamedDataSlot; 即使在同一个线程内.
例如(请注意,BarSlot未在访问TLS的所有特定线程上创建/分配插槽);
public Foo {
private static LocalStorageDataSlot BarSlot = Thread.GetNamedDataSlot("foo_bar");
public static void SetMethodCalledFromManyThreads(string awesome) {
Thread.SetData(BarSlot, awesome);
}
public static void ReadMethodCalledFromManyThreads() {
Console.WriteLine("Data:" + Thread.GetData(BarSlot));
}
}
Run Code Online (Sandbox Code Playgroud)
我问这个问题与代码结构的关系; 任何微观性能提升,如果有的话,都是免费赠品.重用的任何关键问题或性能下降都不是一个可行的选择.
例如,在多线程程序中:
struct TLSObject;
void foo()
{
TLSObject* p = TlsGetValue(slot);
if (p == 0) {
p = new TLSObject;
TlsSetValue(slot, p);
}
// doing something with p
}
Run Code Online (Sandbox Code Playgroud)
第一次在任何线程中调用foo()都会创建一个新的TLSObject。
我的问题是:如何删除TLSObject(如果我不使用boost :: thread和boost :: thread_specific_ptr)?
boost :: thread_specific_ptr可以在线程退出时执行清理工作,但是我想这取决于boost :: thread,不是针对普通的OS线程,而且它很慢。
好吧,说我有
__thread int myVar;
Run Code Online (Sandbox Code Playgroud)
然后我将&myVar从一个线程传递到另一个线程...如果数据真的是"本地",那么1个线程的TLS存储可能无法映射到其他线程的地址空间,事实上,你可能会认为它不应该不会.这会导致SIGSEGV或其他东西.但是,系统可以将相同的地址映射到不同的页面.这是Linux用.tbss/.tdata做的吗?在这种情况下,传递变量的地址将为您提供错误变量的地址!您将获得自己的本地副本,而不是您尝试传递的副本.或者,是否所有内容都共享并映射到不同的虚拟地址 - 允许您传递__thread vars的地址?
显然,应该通过传递其地址来尝试将线程本地存储传递到另一个线程来殴打和鞭打.还有一百万种其他方式 - 例如复制到任何其他变量!但是,如果有人知道,我很好奇.
- 埃文