C# 值类型赋值是原子的吗?

C# Is value type assignment atomic?

值类型的赋值在 .Net 中被认为是原子的吗?

例如,考虑以下程序:

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();
    }
}

像这样的程序会出现 高级 数据竞争吗?甚至 数据竞赛?

我想知道的是:v 是否有可能包含:

结构是值类型。如果将结构分配给 variable/field/method 参数,则整个结构内容将从源存储位置复制到 variable/field/method 参数的存储位置(每种情况下的存储位置都是结构本身)。

不保证复制结构是原子操作。如 C# language specification 中所写:

Atomicity of variable references

Reads and writes of the following data types are atomic: bool, char, byte, sbyte, short, ushort, uint, int, float, and reference types. In addition, reads and writes of enum types with an underlying type in the previous list are also atomic. Reads and writes of other types, including long, ulong, double, and decimal, as well as user-defined types, are not guaranteed to be atomic. Aside from the library functions designed for that purpose, there is no guarantee of atomic read-modify-write, such as in the case of increment or decrement.


所以是的,当一个线程正在从 结构存储位置复制数据时,可能会发生另一个线程出现并开始从另一个结构复制新数据 那个存储位置。因此,从存储位置复制的线程最终可能会混合复制旧数据和新数据。


作为旁注,由于您的一个线程如何写入变量以及另一个线程如何使用该变量,您的代码也可能会遇到其他并发问题。 (用户 acelent 对另一个问题的回答在技术细节上很好地解释了这一点,因此我将仅参考它:)您可以通过封装任何访问来避免此类问题lock 块中的此类 "thread-crossing" 变量。作为 lock 的替代方案,对于基本数据类型,您还可以使用 Interlocked class 提供的方法来访问线程中的线程交叉 variables/fields安全的方式(不过,对于同一个线程交叉变量,在 lockInterlocked 方法之间交替并不是一个好主意)。