53

在 C# 中,只要变量的大小最多native int(即 32 位运行时环境中的 4 个字节和 64 位运行时环境中的 8 个字节),为变量设置值是原子的。在包含所有引用类型和大多数内置值类型(byteshortintlong等)的 64 位环境中。

设置更大的值不是原子的,并且可能导致仅更新部分内存的撕裂。

DateTime是一个结构,它仅包含一个ulong包含其所有数据(TicksDateTimeKind)的字段,并且ulong在 64 位环境中其本身是原子的。

这是否意味着它DateTime也是原子的?或者下面的代码会在某些时候导致撕裂吗?

static DateTime _value;
static void Main()
{
    for (int i = 0; i < 10; i++)
    {
        new Thread(_ =>
        {
            var random = new Random();
            while (true)
            {
                _value = new DateTime((long)random.Next() << 30 | (long)random.Next());
            }
        }).Start();
    }

    Console.ReadLine();
}
4

3 回答 3

34

来自ECMA 规范部分“I.12.6.6 原子读写”

当对一个位置的所有写访问都相同时,符合标准的 CLI 应保证对不大于本机字大小( type 的大小native int)的正确对齐的内存位置的读写访问是原子的(参见 §I.12.6.2)尺寸。原子写入不应改变除已写入的位之外的任何位。除非使用显式布局控制(参见分区 II(控制实例布局))来更改默认行为,否则不大于自然字长(a 的大小native int)的数据元素应正确对齐。对象引用应被视为以本机字长存储。

A在 C# 中native int是 a 。IntPtr

只要sizeof(IntPtr) >= sizeof(DateTime)对于运行时环境(又名:以 64 位运行)是正确的,并且它们不会将内部结构更改为具有未对齐字节而不是[StructLayout(LayoutKind.Auto)]当前具有的显式布局,然后读取和写入DateTime结构(或遵循这些规则的任何其他结构)都被 ECMA 规范保证是原子的。

您可以通过在 64 位环境中运行以下代码来验证这一点:

public unsafe static void Main()
{
    Console.WriteLine(sizeof(DateTime)); // Outputs 8
    Console.WriteLine(sizeof(IntPtr)); // Outputs 8
    Console.WriteLine(sizeof(ulong)); // Outputs 8
}
于 2017-02-15T20:52:51.673 回答
11

运行一些测试并基于上述答案,可以肯定地说它今天是原子的。

我编写了一个测试来验证在 Int64、DateTime 和 3 个 128、192 和 256 大小的自定义结构的 N 个线程上进行 X 次迭代期间可以找到多少撕裂 - 它们的 StructLayout 都没有搞砸。

测试包括:

  1. 将一组值添加到数组中,以便知道它们。
  2. 为每个数组位置设置一个线程,该线程会将数组中的值分配给一个共享变量。
  3. 设置相同数量的线程 (array.length) 以从此共享变量读取到本地。
  4. 检查此本地是否包含在原始数组中。

在我的机器上结果如下(Core i7-4500U, Windows 10 x64, .NET 4.6, Release without debug, Platform target: x64 with code optimization):

-------------- Trying to Tear --------------
Running: 64bits
Max Threads: 30
Max Reruns: 10
Iterations per Thread: 20000
--------------------------------------------
----- Tears ------ | -------- Size ---------
          0             Int64 (64bits)
          0             DateTime (64bits)
         23             Struct128 (128bits)
         87             Struct192 (192bits)
         43             Struct256 (256bits)
----- Tears ------ | -------- Size ---------
          0             Int64 (64bits)
          0             DateTime (64bits)
         44             Struct128 (128bits)
         59             Struct192 (192bits)
         52             Struct256 (256bits)
----- Tears ------ | -------- Size ---------
          0             Int64 (64bits)
          0             DateTime (64bits)
         26             Struct128 (128bits)
         53             Struct192 (192bits)
         45             Struct256 (256bits)
----- Tears ------ | -------- Size ---------
          0             Int64 (64bits)
          0             DateTime (64bits)
         46             Struct128 (128bits)
         57             Struct192 (192bits)
         56             Struct256 (256bits)
------------------- End --------------------

测试代码可以在这里找到:https ://gist.github.com/Flash3001/da5bd3ca800f674082dd8030ef70cf4e

于 2017-02-17T14:10:58.933 回答
1

来自 C# 语言规范。

5.5 变量引用的原子性 以下数据类型的读写是原子的:bool、char、byte、sbyte、short、ushort、uint、int、float 和引用类型。此外,上一个列表中具有基础类型的枚举类型的读取和写入也是原子的。其他类型的读取和写入,包括long、ulong、double 和 decimal,以及用户定义的类型,不保证是原子的。除了为此目的设计的库函数之外,不能保证原子读-修改-写,例如在递增或递减的情况下。

于 2017-02-15T20:54:21.797 回答