Я понимаю, что С# является безопасным языком и не позволяет получить доступ к нераспределенной памяти, кроме как через unsafe
ключевое слово. Однако его модель памяти позволяет переупорядочивать, когда существует несинхронизированный доступ между потоками. Это приводит к опасностям гонки, когда ссылки на новые экземпляры кажутся доступными для гоночных нитей до того, как экземпляры были полностью инициализированы, и является широко известной проблемой для двойной проверки блокировки. Крис Брумме (из команды CLR) объясняет это в своей статье о модели памяти:
Рассмотрим стандартный протокол двойной блокировки:
if (a == null)
{
lock(obj)
{
if (a == null)
a = new A();
}
}
Это обычная техника, позволяющая избежать блокировки чтения "a" в типичном случае. Он отлично работает на X86. Но это будет нарушено юридической, но слабой реализацией спецификации CLI ECMA. Его правда, что, согласно спецификации ECMA, приобретение блокировки приобретает семантику, а релиз блокировки имеет семантику выпуска.
Однако мы должны предположить, что во время строительства "a. Эти магазины могут быть произвольно переупорядочены, в том числе возможность отложить их до публикации издательского магазина, который присваивает новый объект "a". В этот момент перед магазином есть небольшое окно. Исключение подразумевается выходом из замка. Внутри этого окна другие процессоры могут перемещаться по ссылке 'a и видеть частично сконструированный экземпляр.
Меня всегда путают то, что означает "частично построенный экземпляр". Предполагая, что среда выполнения.NET очищает память при распределении, а не сборку мусора (обсуждение), означает ли это, что другой поток может читать память, которая по-прежнему содержит данные из собранных мусором объектов (например, что происходит на небезопасных языках)?
Рассмотрим следующий конкретный пример:
byte[] buffer = new byte[2];
Parallel.Invoke(
() => buffer = new byte[4],
() => Console.WriteLine(BitConverter.ToString(buffer)));
Вышеуказанное условие гонки; выход будет либо 00-00
либо 00-00-00-00
. Однако возможно ли, что второй поток считывает новую ссылку на buffer
до того, как память массива была инициализирована до 0, и вместо этого выведет другую произвольную строку?