What is the safest (and shortest) way do lock read/write access to static
members in a multithreaded environment in C#?
Is it possible to do the thread
For small values (basically any field that can be declared volatile), you can do the following:
private static volatile int backingField;
public static int Field
{
get { return backingField; }
set { backingField = value; }
}
With large values the assignment won't be atomic if the value is larger then 32-bits on a 32-bit machine or 64-bits on a 64-bit machine. See the ECMA 335 12.6.6 spec. So for reference types and most of the built-in value types the assignment is atomic, however if you have some large struct, like:
struct BigStruct
{
public long value1, valuea0a, valuea0b, valuea0c, valuea0d, valuea0e;
public long value2, valuea0f, valuea0g, valuea0h, valuea0i, valuea0j;
public long value3;
}
In this case you will need some kind of locking around the get accessor. You could use ReaderWriterLockSlim for this which I've demonstrated below. Joe Duffy has advice on using ReaderWriterLockSlim
vs ReaderWriterLock
:
private static BigStruct notSafeField;
private static readonly ReaderWriterLockSlim slimLock =
new ReaderWriterLockSlim();
public static BigStruct Safe
{
get
{
slimLock.EnterReadLock();
var returnValue = notSafeField;
slimLock.ExitReadLock();
return returnValue;
}
set
{
slimLock.EnterWriteLock();
notSafeField = value;
slimLock.ExitWriteLock();
}
}
Unsafe Get-Accessor Demonstration
Here's the code I used to show the lack of atomicity when not using a lock in the get-accessor:
private static readonly object mutexLock = new object();
private static BigStruct notSafeField;
public static BigStruct NotSafe
{
get
{
// this operation is not atomic and not safe
return notSafeField;
}
set
{
lock (mutexLock)
{
notSafeField = value;
}
}
}
public static void Main(string[] args)
{
var t = new Thread(() =>
{
while (true)
{
var current = NotSafe;
if (current.value2 != (current.value1 * 2)
|| current.value3 != (current.value1 * 5))
{
throw new Exception(String.Format("{0},{1},{2}", current.value1, current.value2, current.value3));
}
}
});
t.Start();
for(int i=0; i<50; ++i)
{
var w = new Thread((state) =>
{
while(true)
{
var index = (int) state;
var newvalue = new BigStruct();
newvalue.value1 = index;
newvalue.value2 = index * 2;
newvalue.value3 = index * 5;
NotSafe = newvalue;
}
});
w.Start(i);
}
Console.ReadLine();
}
You should lock/unlock on each static member access, within the static accessor, as needed.
Keep a private object to use for locking, and lock as required. This keeps the locking as fine-grained as possible, which is very important. It also keeps the locking internal to the static class members. If you locked at the class level, your callers would become responsible for the locking, which would hurt usability.