这个问题使我想知道Java和.NET等高级开发框架中的线程本地存储。
Java有一个ThreadLocal<T>类(也许还有其他结构),而.NET有数据插槽,很快就有ThreadLocal<T>了自己的类。(它也具有ThreadStaticAttribute,但我对成员数据的线程本地存储特别感兴趣。)大多数其他现代开发环境都在语言或框架级别为其提供了一种或多种机制。
ThreadLocal<T>
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(); }
我可以看到,将单个对象与线程本地存储一起使用会稍微提高内存效率,并且由于分配(和构造)较少而需要更少的处理器资源。还有其他优势吗?
线程本地存储解决了哪些问题,或者与创建独立对象实例以包含线程本地数据的标准的面向对象的习语相比,线程本地存储提供了哪些优势?
线程本地存储允许您为每个正在运行的线程提供一个类的唯一实例,这在尝试使用非线程安全类或避免由于共享状态而可能发生的同步要求时非常有用。
至于与示例相比的优势-如果生成单个线程,则与传递实例相比,使用线程本地存储几乎没有优势。 ThreadLocal<T>但是,当(直接或间接)使用ThreadPool时,类似的构造变得非常有价值。
例如,我最近有一个特定的工作过程,在该过程中,我们使用.NET中的新任务并行库进行大量的计算。可以对执行的计算的某些部分进行缓存,如果缓存中包含特定的匹配项,则在处理一个元素时可以节省大量时间。但是,缓存的信息对内存的要求很高,因此我们不希望缓存的内容超过最后一个处理步骤。
但是,尝试在线程之间共享此缓存是有问题的。为了做到这一点,我们必须同步对它的访问,并在类内部添加一些额外的检查以使它们线程安全。
而不是这样做,我重写了算法,以允许每个线程在中维护自己的私有缓存ThreadLocal<T>。这允许线程各自维护自己的私有缓存。由于TPL使用的分区方案倾向于将元素块保持在一起,因此每个线程的本地缓存倾向于包含其所需的适当值。
这消除了同步问题,但也使我们能够将缓存保持在原位。在这种情况下,整体收益非常大。
作为一个更具体的示例,请看一下我使用TPL编写的有关聚合的博客文章。在内部,ThreadLocal<TLocal>每当您使用ForEach重载时,Parallel类都会使用a 来保持局部状态(以及Parallel.For<TLocal>方法)。这是将本地状态与每个线程分开的方式,以避免锁定。
ThreadLocal<TLocal>
Parallel.For<TLocal>