It is my understanding that C# is a safe language and doesn't allow one to access unallocated memory, other than through the unsafe
keyword. However, its memory model allows reordering when there is unsynchronized access between threads. This leads to race hazards where references to new instances appear to be available to racing threads before the instances have been fully initialized, and is a widely known problem for double-checked locking. Chris Brumme (from the CLR team) explains this in their Memory Model article:
Consider the standard double-locking protocol:
if (a == null)
{
lock(obj)
{
if (a == null)
a = new A();
}
}
This is a common technique for avoiding a lock on the read of ‘a’ in the typical case. It works just fine on X86. But it would be broken by a legal but weak implementation of the ECMA CLI spec. It’s true that, according to the ECMA spec, acquiring a lock has acquire semantics and releasing a lock has release semantics.
However, we have to assume that a series of stores have taken place during construction of ‘a’. Those stores can be arbitrarily reordered, including the possibility of delaying them until after the publishing store which assigns the new object to ‘a’. At that point, there is a small window before the store.release implied by leaving the lock. Inside that window, other CPUs can navigate through the reference ‘a’ and see a partially constructed instance.
I've always been confused by what "partially constructed instance" means. Assuming that the .NET runtime clears out memory on allocation rather than garbage collection (discussion), does this mean that the other thread might read memory that still contains data from garbage-collected objects (like what happens in unsafe languages)?
Consider the following concrete example:
byte[] buffer = new byte[2];
Parallel.Invoke(
() => buffer = new byte[4],
() => Console.WriteLine(BitConverter.ToString(buffer)));
The above has a race condition; the output would be either 00-00
or 00-00-00-00
. However, is it possible that the second thread reads the new reference to buffer
before the array's memory has been initialized to 0, and outputs some other arbitrary string instead?