Может ли DateTime разорваться в 64-битной среде?

В C # установка значения переменной является атомарной, если ее размер не превышает native int (т.е. 4 байта в 32-битной среде выполнения и 8 байтов в 64-битной). В 64-битной среде, которая включает все типы ссылок и большинство встроенных типов значений (byte, short, int, long и т. Д.).

Установка большего значения не атомарна и может вызвать разрыв, когда обновляется только часть памяти.

DateTime - это структура, которая включает только одно поле ulong, содержащее все его данные (Ticks и DateTimeKind), а 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();
}

person i3arnon    schedule 15.02.2017    source источник
comment
наверное, самый окончательный ответ, который вы можете получить: я так не думаю.   -  person Mike Nakis    schedule 15.02.2017
comment
@MikeNakis Я тоже так не думаю, но реализация ConcurrentDictionary не рассматривает DateTime как атомарность, что заставляет меня задуматься: ConcurrentDictionary.IsValueWriteAtomic   -  person i3arnon    schedule 15.02.2017
comment
@ i3arnon: в коде перечислены несколько целочисленных типов с комментарием, указывающим на ссылку CLI. Ссылка CLI касается только размеров, а не типов, и я серьезно сомневаюсь, что DateTime когда-либо будет использовать явно смещенное поле, поэтому я думаю, что можно с уверенностью заключить, что DateTime является атомарным.   -  person Stephen Cleary    schedule 15.02.2017
comment
Это довольно странно. В стандарте говорится, что все изменения значений, не превышающих собственный размер int, должны быть атомарными, и DateTime соответствует этой категории в 64-битной системе. Однако ConcurrentDictionary кажется гораздо более консервативным, чем стандарт: он не будет рассматривать no-large-than-native-int structs как атомарные, он будет рассматривать только встроенные примитивные типы данных как атомарные. Принято считать, что это, вероятно, недостаток в ConcurrentDictionary, но обратите внимание, как я пишу комментарий, а не ответ! C -: =   -  person Mike Nakis    schedule 15.02.2017
comment
Для меня более серьезный вопрос: зачем тебе все равно? Зачем тратить все это время, пытаясь понять это, вместо того, чтобы просто предполагать, что это небезопасно и писать свой код, исходя из этого предположения? Я не уверен, что рассчитываю на то, что MS будет следовать спецификации, даже если там сказано, что это безопасно. ;) Мне кажется, что это проблема XY.   -  person jpmc26    schedule 16.02.2017
comment
@ jpmc26 просто установка значения вместо взятия блокировки чрезвычайно полезна в сценариях с высокой степенью параллелизма. Я забочусь, потому что забота позволяет мне преодолевать узкие места и улучшать производительность моего продукта.   -  person i3arnon    schedule 16.02.2017
comment
@HansPassant Почему это проблема? Что вызывает установка автоматического макета?   -  person i3arnon    schedule 16.02.2017
comment
@ i3arnon - наверное такие забавные вещи. Я не знаю о других махинациях. Лично мне больше не нравится DateTime et al. и форма API (который, к сожалению, работает достаточно хорошо для простых случаев). Есть некоторые случайные ошибки, из-за которых некоторые варианты использования невозможно решить изначально.   -  person Clockwork-Muse    schedule 16.02.2017
comment
@ i3arnon Если есть сомнения, я бы сохранил отметки в поле long и заключил бы его в свойство DateTime. Здесь нулевой риск.   -  person Eli Arbel    schedule 16.02.2017
comment
Я написал тестируемую версию gist.github.com/Flash3001/ec0da534167bd8cdcd3cd   -  person Lucas Teixeira    schedule 16.02.2017
comment
Нет, DateTime не был бы атомарным? Я ожидал этого, поскольку DateTime - это структура, к которой применяются правила структуры. Тот факт, что его базовое значение использует собственный размер слова, не имеет значения. За исключением случаев, когда объект выделяется / "конструируется", разве я не должен предполагать, что он подвержен проблемам с параллелизмом в разных областях? Боковое примечание: я считаю, что встроенный класс .net Interlocked (System.Threading.Interlocked) очень полезен в реальном мире, где я знаю, что использую Int64 в тысячах 32-битных сред выполнения, которые находятся на 32-битных ядрах.   -  person Sql Surfer    schedule 18.02.2017


Ответы (3)


Из раздела спецификации ECMA «I.12.6. .6 Атомарные чтения и записи "

Соответствующий интерфейс командной строки должен гарантировать, что доступ для чтения и записи к правильно выровненным ячейкам памяти, не превышающим собственный размер слова (размер типа native int), является атомарным (см. §I.12.6.2), когда все доступы для записи к местоположению являются тот же размер. Атомарные записи не должны изменять никаких битов, кроме записанных. Если явное управление макетом (см. Раздел II (Управление макетом экземпляра)) не используется для изменения поведения по умолчанию, элементы данных, не превышающие естественный размер слова (размер native int), должны быть правильно выровнены. Ссылки на объекты должны обрабатываться так, как если бы они сохранялись с исходным размером слова.

A native int is a IntPtr in C#.

Пока 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
}
person Scott Chamberlain    schedule 15.02.2017
comment
Но вопрос стоит: sizeof(IntPtr) == sizeof(DateTime)? DateTime содержит только ulong, но означает ли это sizeof(DateTime) == sizeof(ulong) == sizeof(IntPtr) == 8? - person i3arnon; 16.02.2017
comment
Да, все 3 вывода 8 в 64-битной среде, согласно спецификации, являются атомарными. В 32-битной среде DateTime и ulong возвращают 8, но IntPtr возвращает 4, что нарушает правило sizeof(IntPtr) >= sizeOf(DateTime), поэтому не гарантируется атомарность (это то, что мы ожидали) - person Scott Chamberlain; 16.02.2017
comment
Постойте, а что значит все 3 вывода 8? Как вы запускаете sizeof(DateTime)? Я не могу скомпилировать это и получить DateTime не имеет предопределенного размера. Вы можете использовать Marshal.SizeOf, но это проверяет размер при сортировке. - person i3arnon; 16.02.2017
comment
IsReadsAndWritesGuaranteedAtomic - ›AreReadsAndWritesGuaranteedAtomic? или, может быть, ReadsAndWritesAreGuaranteedAtomic было бы лучше. - person jpmc26; 16.02.2017
comment
@ i3arnon вот точный код, который я запускал, вам просто нужно пометить сборку как небезопасную в настройки сборки проекта, однако я обновил свой пример функции в конце, чтобы использовать небезопасный код вместо класса маршала, потому что вы правильно отметили потенциальную разницу в размере. - person Scott Chamberlain; 16.02.2017
comment
Вы также можете использовать пакет NuGet System.Runtime.CompilerServices.Unsafe и вызвать Unsafe.SizeOf<T>(), который работает с любым типом. Эта библиотека была написана на IL и предоставляет некоторые низкоуровневые функции, недоступные в C #. - person Eli Arbel; 16.02.2017
comment
@ScottChamberlain, не возражаете, если я (или вы) добавлю этот код к ответу? Я думаю, что возможность запускать sizeof(DateTime) - это наиболее окончательный ответ, который мы получим. - person i3arnon; 17.02.2017
comment
@ i3arnon совсем нет, не стесняйтесь. - person Scott Chamberlain; 17.02.2017
comment
Метод AreReadsAndWritesAreGuaranteedAtomic здесь не проверяет наличие только одного поля (struct с двумя int полями не нужно записывать как одну 64-битную запись, а скорее он содержит две ячейки памяти, которые могут быть записаны как две записи, которые являются атомарными по отдельности, но не атомарными как пара) и не проверяет наличие StructLayoutAttribute. - person Jon Hanna; 21.02.2017
comment
@JonHanna type.IsAutoLayout действительно проверяет StructLayoutAttribute, это удобное свойство, которое не требует извлечения атрибута. Однако я сообщу вам, что пример с двумя int вернет sizeof(example) == 8 на 64-битной машине и не будет работать. Я убрал функцию из вопроса. - person Scott Chamberlain; 21.02.2017
comment
Правильно, вы находитесь на IsAutoLayout, мои глаза как-то пропустили это мимо. - person Jon Hanna; 21.02.2017
comment
@JonHanna больше об этом думает, я не знаю, как CLR обрабатывает вашу ситуацию с двумя int, он может рассматривать структуру как один 8-байтовый объект, а не два 4-байтовых объекта при выполнении присваивания, поэтому он все еще может быть атомарным. Это неясно на основе спецификации, я все равно собираюсь оставить эту функцию отключенной, но, возможно, стоит протестировать. - person Scott Chamberlain; 21.02.2017

Запустив несколько тестов и основываясь на приведенном выше ответе, можно с уверенностью сказать, что сегодня он атомарный.

Я написал тест, чтобы проверить, сколько разрывов можно найти во время X итераций по N потокам для Int64, DateTime и 3 пользовательских структур размером 128, 192 и 256 - ни одна из них с их StructLayout не испортилась.

Тест состоит из:

  1. Добавление набора значений в массив, чтобы они были известны.
  2. Устанавливая один поток для каждой позиции массива, этот поток присваивает значение из массива общей переменной.
  3. Установка того же количества потоков (array.length) для чтения из этой общей переменной в локальную.
  4. Проверьте, содержится ли этот локальный объект в исходном массиве.

На моем компьютере результаты следующие (Core i7-4500U, Windows 10 x64, .NET 4.6, выпуск без отладки, цель платформы: x64 с оптимизацией кода):

-------------- 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/da5bd3ca800f674082dd / а>

person Lucas Teixeira    schedule 17.02.2017

Из спецификации языка C #.

5.5 Атомарность ссылок на переменные Чтение и запись следующих типов данных являются атомарными: bool, char, byte, sbyte, short, ushort, uint, int, float и ссылочные типы. Кроме того, чтение и запись перечислимых типов с базовым типом в предыдущем списке также являются атомарными. Чтение и запись других типов, включая long, ulong, double и decimal, а также определяемые пользователем типы, не гарантируются как атомарные. Помимо библиотечных функций, предназначенных для этой цели, нет никакой гарантии атомарного чтения-изменения-записи, например, в случае увеличения или уменьшения.

person OmariO    schedule 15.02.2017
comment
В принципе это так, но мы часто создаем типы с более строгими гарантиями, так что это не ответ на вопрос. - person Immo Landwerth; 17.02.2017