Is the assignement of a value type considered to be atomic in .Net?
For example, consider the following program:
struct Vector3
{
public float X { get; private set; }
public float Y { get; private set; }
public float Z { get; private set; }
public Vector3(float x, float y, float z)
{
this.X = x;
this.Y = y;
this.Z = z;
}
public Vector3 Clone()
{
return new Vector3(X, Y, Z);
}
public override String ToString()
{
return "(" + X + "," + Y + "," + Z + ")";
}
}
class Program
{
private static Vector3 pos = new Vector3(0,0,0);
private static void ReaderThread()
{
for (int i = 0; i < int.MaxValue; i++)
{
Vector3 v = pos;
Console.WriteLine(v.ToString());
Thread.Sleep(200);
}
}
private static void WriterThread()
{
for (int i = 1; i < int.MaxValue; i++)
{
pos = new Vector3(i, i, i);
Thread.Sleep(200);
}
}
static void Main(string[] args)
{
Thread w = new Thread(WriterThread);
Thread r = new Thread(ReaderThread);
w.Start();
r.Start();
}
}
Can a program like this suffer from a High-Level data race? Or even a Data Race?
What I want to know here is: is there any possibility that v will either contain:
- Garbage values due to a possible data race
- Mixed components X, Y or Z that refer to both pos before assignement and pos after assignment. For example, if pos = (1,1,1) and then pos is assigned the new value of (2,2,2) can v = (1,2,2)?
Structs are value types. If you assign a struct to a variable/field/method parameter, the whole struct content will be copied from the source storage location to the storage location of the variable/field/method parameter (the storage location in each case being the size of the struct itself).
Copying a struct is not guaranteed to be an atomic operation. As written in the C# language specification:
So yes, it can happen that while one thread is in the process of copying the data from a struct storage location, another thread comes along and starts copying new data from another struct to that storage location. The thread copying from the storage location thus can end up copying a mix of old and new data.
As a side note, your code can also suffer from other concurrency problems due to how one of your threads is writing to a variable and how the variable is used by another thread. (An answer by user acelent to another question explains this rather well in technical detail, so i will just refer to it: https://stackoverflow.com/a/46695456/2819245) You can avoid such problems by encapsulating any access of such "thread-crossing" variables in a
lock
block. As an alternative tolock
, and with regard to basic data types, you could also use methods provided by theInterlocked
class to access thread-crossing variables/fields in a thread-safe manner (Alternating between bothlock
andInterlocked
methods for the same thread-crossing variable is not a good idea, though).