Subterranean IL: ThreadLocal revisitedPublished 18 April 2013 4:07 pm
Last year, I looked at the
ThreadLocal type as it exists in .NET 4. In .NET 4.5, this type has been completely rewritten. In this post, I’ll be looking at how the new
ThreadLocal works in .NET 4.5. I won’t be looking at all the implementation details, but concentrating on how this type works. Again, it’s recommended you have the type open in a decompiler.
No More Generics!
The most obvious change is the lack of generic classes – it no longer uses generic instantiations to store individual thread static variables. Instead, it uses a design similar to that of
ConcurrentBag – an array of values held in a thread static array, with each instance of
ThreadLocal being assigned its own index into that array, and linked lists between the items in each thread static array to allow access from any thread.
The important variables here are the thread static
m_linkedSlot. Each thread has its own
ts_slotArray instance, and each instance of
ThreadLocal has its own slot index into those arrays as
m_idComplement (I’m ignoring the fact that
ThreadLocal is generic for now; each generic instantiation of
ThreadLocal has its own static variables independant from any other). The list of all values stored in each instance is accessible through the linked list accessible from
However, these extra links between arrays mean that the value to be stored can’t be put straight into
ts_slotArray, you need an extra type to provide these links. This is where the
LinkedSlot type comes in – it provides a
Previous fields to link between slots in different arrays. This graph indicates how these different fields interact – the arrows represent the
Previous references between slots:
Note that the instance of
LinkedSlot directly referenced by the
m_linkedSlot field is an empty instance that is not stored in any array; it exists only to be the target of another slot’s
Previous field, and simplifies the logic in the other methods.
Each instance of
ThreadLocal is assigned a unique index by the
IdManager class when it is created. When a thread first sets a value in an instance of
ThreadLocal, the following happens in
- If the slot array hasn’t been assigned for this thread (ie this is the first time this thread has accessed any instance of
ThreadLocal), it creates a new array to hold enough items for this instance’s slot index.
- If the array isn’t big enough for this instance’s slot index, it is resized so it is, and all the containing
LinkedSlots are updated to point to the new array (in the
CreateLinkedSlotis called to create a new
LinkedSlotinstance and store it in the array at the instance’s slot index. It also adds it to the head of the linked list pointed to by
m_linkedSlotin this instance of
Subsequently, when values are get & set, it gets and sets the value at the slot index owned by the
ThreadLocal being accessed, in the slot array for the accessing thread.
Removing and disposing of ThreadLocals
So that’s what happens when values are set. What about when the thread is no longer running, or the
ThreadLocal is disposed? Both require values to be removed or unset in the arrays & untangled in the linked lists, else any values set will just stay there, won’t be collected, and will cause a memory leak.
When an instance of
ThreadLocalis disposed or finalized, it needs to clear the instances of
LinkedSlotin all the referenced slot arrays. Fortunately, this is quite easy to do – it simply iterates through the link list defined by
m_linkedSlot, and clears the entries. Finally, it returns the slot index it was using to the
IdManagerclass to be reused when the next instance of
Dealing with a thread exit is harder, as there isn’t a global event that fires whenever a thread exits. Fortunately, a little-known feature of thread statics can be used to clear up the slot array belonging to a thread that has exited.
Detecting thread exits
Normal static fields, once the type has been initialized, stay around until the AppDomain exits. That means that any object being referenced by a static field won’t be collected until the field is explicitly cleared.
However, thread static fields are different. The CLR keeps track of which threads are active, and which have exited. It can link this to the various values stored in a thread static field. This means that any value set on a thread static field belonging to a thread that has exited, and that isn’t referenced by anything else, is eligible for garbage collection, and will be collected the next time the garbage collector runs.
This feature is exploited by
ThreadLocal to clear up the slot arrays of exited threads. This is primarily performed by the
FinalizationHelper class, which is created and assigned to a thread static field when the slot array is first created and assigned.
This class only exists for its finalizer. When a thread exits, the corresponding instance of
FinalizationHelper assigned to the
ts_finalizationHelper field becomes eligible for collection. If and when the garbage collector runs, this instance gets collected, and the finalizer is run. This finalizer removes any non-empty slots from the linked lists of active
ThreadLocal instances, unless the values are needed to be kept if a call to
ThreadLocal.Values is made to return all the values ever set on that instance.
So there we are; the upgraded
ThreadLocal. It’s an improvement on the old version, in that it allows access to all the values ever set on an instance of
ThreadLocal, it doesn’t fallback on the thread local data store, and it doesn’t pollute the namespace with thousands of generic instances of holder classes. Much better!