Introduction
Suppose you're writing a multi-threaded application and you want each thread to
have its own copy of some data. You also want this data to persist throughout
the lifetime of the thread.
As each thread has its own stack, it also has its own copy of any local
variables. However, the trouble with these is that they are not persistent - once
the current method has finished executing they're destroyed.
Also local variables are scoped to the current method and so, if you want them
to be available to other methods which the current method calls, then you need
to pass them as parameters. This can get rather messy if there are several such
variables and multiple method calls.
You could use a static field to store persistent data but then there's only one
copy of the data which all threads share.
So is there anything you can use so that each thread has its own copy of
persistent data?
The answer is Thread-Local Storage or TLS for short.
There are three ways to create TLS using a .NET application each of which is
discussed in turn below.
The ThreadStatic attribute
If you decorate a static field with the ThreadStatic attribute, then each thread
will have its own copy of that field. Here's an example:
using
System;
using
System.Threading;
class
ThreadStaticTest
{
[ThreadStatic]
static string
greeting;
static void
Main()
{
greeting = "Goodbye from the main thread";
Thread t =
new Thread(ThreadMethod);
t.Start();
t.Join();
Console.WriteLine(greeting);
// prints the main thread's copy
Console.ReadKey();
}
static void
ThreadMethod()
{
greeting = "Hello from the second thread";
// only affects the second thread's copy
Console.WriteLine(greeting);
}
}
The output is:
However, the ThreadStatic attribute has a couple of shortcomings:
It doesn't work with instance fields.
If you initialize the static field to some non-default value (either where it's defined or using a static constructor), then it gets initialized only once for the thread which is running at the time. Other threads only get its default value.
The effect of the second problem can be seen by
running this modified version of the above program:
class
ThreadStaticTest2
{
[ThreadStatic]
static string
greeting = "Greetings from the current thread";
static void
Main()
{
Console.WriteLine(greeting);
// prints initial value
greeting = "Goodbye from the main
thread";
Thread t =
new Thread(ThreadMethod);
t.Start();
t.Join();
Console.WriteLine(greeting);
// prints the main thread's copy
Console.ReadKey();
}
static void
ThreadMethod()
{
Console.WriteLine(greeting);
// prints nothing as greeting initialized on main
thread
greeting = "Hello from the second
thread"; // only affects the second thread's
copy
Console.WriteLine(greeting);
}
}
The output is now:
Notice that the second line is blank because the second thread's copy of
'greeting' is null, initially.
The ThreadLocal<T> class
This is a class which was introduced in .NET 4.0 and which solves the problems
with the ThreadStatic attribute.
Here's a similar program but this time using ThreadLocal<T> for both an instance
and a static field and providing them with initial non-default values:
using
System;
using
System.Threading;
class
ThreadLocalTest
{
static
ThreadLocal<string> greeting;
ThreadLocal<int>
numThreads = new
ThreadLocal<int>(() => 1);
// instance field
static ThreadLocalTest()
{
greeting = new
ThreadLocal<string>(()
=> "Greetings from the current thread");
}
static void
Main()
{
Console.WriteLine(greeting.Value.Replace("current",
"main"));
greeting.Value = "Goodbye from the main
thread";
Thread t =
new Thread(ThreadMethod);
t.Start();
t.Join();
ThreadLocalTest tl =
new ThreadLocalTest();
Thread t2 =
new Thread(tl.InstanceThreadMethod);
t2.Start();
t2.Join();
Console.WriteLine("The
number of current threads is now " + tl.numThreads);
Console.WriteLine(greeting.Value);
// prints the main thread's copy which is still 1
Console.ReadKey();
}
static void
ThreadMethod()
{
Console.WriteLine(greeting.Value.Replace("current",
"second"));
greeting.Value = "Hello from the second
thread"; // only affects the second thread's
copy
Console.WriteLine(greeting.Value);
}
void InstanceThreadMethod()
{
numThreads.Value++; // increment this thread's
copy to 2
Console.WriteLine("The
number of current threads is " + this.numThreads);
}
}
The output is:
Notice that the ThreadLocal<T> constructor takes a Func<T> argument i.e. a
delegate for a method with no parameters which returns a value of type T. Here
we're passing it a compatible lambda expression.
Also the thread local variable is initialized 'lazily' i.e. it's not initialized
until a thread first attempts to retrieve its Value property. You can check
whether it's been initialized yet for the current thread with the IsValueCreated
property.
Thread.SetData and Thread.GetData
These two static methods store and retrieve data for each thread in a local data
store 'slot'. If you give this slot a name then it can be accessed throughout
the application using that name, no matter where it was originally allocated.
This doesn't apply to unnamed slots which are only accessible if allocated
within the current scope or exposed via a globally accessible variable.
Each thread in the application can store its own copy of the data within a
single named or unnamed slot.
Here's a version of the earlier programs which uses a named slot:
using
System;
using
System.Threading;
class
ThreadNamedSlotTest
{
static
LocalDataStoreSlot greeting = Thread.AllocateNamedDataSlot("greeting");
static void
Main()
{
Thread.SetData(greeting,
"Goodbye from the main thread");
Thread t =
new Thread(ThreadMethod);
t.Start();
t.Join();
Console.WriteLine(Thread.GetData(greeting));
// prints the main thread's copy
Thread.FreeNamedDataSlot("greeting");
// releases across all threads
Console.ReadKey();
}
static void
ThreadMethod()
{
Thread.SetData(greeting,
"Hello from the second thread");
// only affects the second thread's copy
Console.WriteLine(Thread.GetData(greeting));
OtherClass oc =
new OtherClass();
oc.Print();
}
}
class
OtherClass
{
public void
Print()
{
Console.WriteLine("The
next line is printed from other class ...");
LocalDataStoreSlot greeting =
Thread.GetNamedDataSlot("greeting");
Console.WriteLine(Thread.GetData(greeting));
}
}
The output is:
To use an unnamed slot, the declaration of the 'greeting' field would need to be
changed to:
static
LocalDataStoreSlot greeting =
Thread.AllocateDataSlot();
However, unless you made this field public, it would not then be accessible from
OtherClass.
Using data slots has a couple of drawbacks:
Conclusion
Prior to the advent of .NET 4.0, thread-local storage was a little known (and
understood) aspect of .NET's threading support even though the underlying
mechanism is provided by the operating system itself.
However, it has assumed a greater importance with the introduction of parallel
programming support and the ThreadLocal<T> class in .NET 4.0. This is because it
enables each thread to access its own copy of an object rather than a single
shared object and thereby achieve thread-safety without the need for locks.
The examples used in this article are also available in the accompanying
download for anyone who would like to play around with the code.