实例级线程局部存储有哪些优点?

这个问题让我想到了Java和.NET等高级开发框架中的线程局部存储 。

Java有一个ThreadLocal类(可能还有其他构造),而.NET有数据槽 ,很快就有自己的ThreadLocal类。 (它还有ThreadStaticAttribute ,但我对成员数据的线程局部存储特别感兴趣。)大多数其他现代开发环境为语言或框架级别提供了一种或多种机制。

线程局部存储解决了什么问题,或者线程局部存储提供了什么优势,而不是创建单独的对象实例以包含线程本地数据的标准面向对象的习惯用法? 换句话说,这是怎么回事:

 // Thread local storage approach - start 200 threads using the same object // Each thread creates a copy of any thread-local data ThreadLocalInstance instance = new ThreadLocalInstance(); for(int i=0; i < 200; i++) { ThreadStart threadStart = new ThreadStart(instance.DoSomething); new Thread(threadStart).Start(); } 

优于此?

 // Normal oo approach, create 200 objects, start a new thread on each for(int i=0; i < 200; i++) { StandardInstance standardInstance = new StandardInstance(); ThreadStart threadStart = new ThreadStart(standardInstance.DoSomething); new Thread(threadStart).Start(); } 

我可以看到,使用具有线程本地存储的单个对象可以稍微提高内存效率,并且由于分配(和构造)较少而需要较少的处理器资源。 还有其他优点吗?

线程局部存储解决了什么问题,或者线程局部存储提供了什么优势,而不是创建单独的对象实例以包含线程本地数据的标准面向对象的习惯用法?

线程本地存储允许您为每个正在运行的线程提供一个类的唯一实例,这在尝试使用非线程安全类时非常有用,或者在尝试避免由于共享状态而可能发生的同步要求时非常有用。

至于优势与您的示例 – 如果您正在生成单个线程,使用线程本地存储而不是传入实例几乎没有优势。 然而,当使用ThreadPool(直接或间接)工作时, ThreadLocal和类似的构造变得非常有价值。

例如,我最近有一个特定的过程,我们正在使用.NET中的新任务并行库进行一些非常繁重的计算。 执行的计算的某些部分可以被缓存,并且如果缓存包含特定匹配,我们可以在处理一个元素时节​​省相当多的时间。 但是,缓存的信息具有较高的内存要求,因此我们不希望缓存超过上一个处理步骤。

但是,尝试跨线程共享此缓存是有问题的。 为了做到这一点,我们必须同步对它的访问,并在我们的类中添加一些额外的检查,以使它们的线程安全。

我没有这样做,而是重写了算法,允许每个线程在ThreadLocal维护自己的私有缓存。 这允许每个线程维护自己的私有缓存。 由于TPL使用的分区方案倾向于将元素块保持在一起,因此每个线程的本地高速缓存倾向于包含它所需的适当值。

这消除了同步问题,但也使我们能够保持缓存。 在这种情况下,整体效益非常大。

有关更具体的示例,请查看我在使用TPL汇总时编写的博客文章。 在内部,只要您使用保持本地状态的ForEach重载 (以及Parallel.For方法),Parallel类就使用ThreadLocal 。 这是每个线程保持本地状态分开以避免锁定的方式。

偶尔,有线程本地状态是有帮助的。 一个示例是日志上下文 – 设置您当前正在服务的请求的上下文或类似内容可能很有用,这样您就可以整理所有日志以处理该请求。

另一个很好的例子是.NET中的System.Random 。 相当常见的知识是,每次要使用Random时都不应创建新实例,因此有些人创建单个实例并将其放在静态变量中……但这很尴尬,因为Random不是线程安全的。 相反,你真的想要每个线程一个实例,适当地播种。 ThreadLocal非常适用于此。

类似的例子是与线程相关的文化或安全上下文。

一般来说,这是一个不想在整个地方传递太多上下文的情况。 你可以让每一个方法调用都包含一个“RandomContext”或一个“LogContext” – 但它会妨碍你的API的清洁 – 如果你不得不调用另一个可以回调给它的API那么链就会被打破你的虚拟方法或类似的东西。

在我看来,线程本地数据应尽可能避免 – 但偶尔它可能真的很有用。

我会说,在大多数情况下,你可以逃避它是静态的 – 但偶尔你可能想要每个实例,每线程信息。 再次,值得用你的判断来看看它有用的地方。

它有助于将值传递到堆栈中。 当您需要调用堆栈中的值时,它很方便,但没有办法(或好处)将此值传递给作为方法参数所需的位置。 上面将当前HttpRequest存储在ThreaLocal中的示例就是一个很好的例子:另一种方法是将HttpRequest作为参数传递到堆栈中,直到它需要的位置。

在Java中,线程本地存储在Web应用程序中非常有用,其中单个请求通常由给定的线程处理。 以Spring Security为例,安全filter将执行身份validation,然后将用户凭据存储在Thread局部变量中。

这允许实际的请求处理代码能够访问当前用户请求/认证信息,而无需向代码注入任何其他内容。

以下是ThreadLocal的实际用法: http : //blogs.captechconsulting.com/blog/balaji-muthuvarathan/persistence-pattern-using-threadlocal-and-ejb-interceptors

你想进行一系列调用,无处不在地访问一些变量。 您可以在每个调用中将其作为参数传递

 function startComputingA(other args) { global_v = create // declared locally call A2(other args, global_v) call A3(other args, global_v) function A2(other args, global_v) { call A3(other args, global_v) function A3(other args, global_v) { call A4(other args, global_v) 

您的所有函数都必须声明global_v参数。 这很糟糕。 您具有存储全局变量的全局范围,并将其“虚拟地”路由到每个例程

 variable global_v; function A() { // use global_v and call B() } function B() { // use global_v and call C() } 

然而,另一个线程可能同时开始执行其中一些function。 这会破坏你的全局变量。 因此,您希望变量对于所有例程都是全局可见的,而不是在线程之间。 您希望每个线程都有一个global_v的单独副本。 这是本地存储必不可少的时候! 您将global_v声明为线程局部变量。 因此,任何线程都可以从任何地方访问global_v ,但不同的副本。