Рассмотрим следующий код C# в многоядерной / многопроцессорной среде, работающей на x64 или ARM:
public sealed class Trio
{
public long A;
public long B;
public long C;
}
public static class MP
{
private static readonly object locker = new object();
private static readonly Trio Data = new Trio();
public static Trio ReadCopy()
{
lock (locker)
{
return new Trio { A = Data.A, B = Data.B, C = Data.C };
}
}
public static void Set(long a, long b, long c)
{
lock (locker)
{
Data.A = a;
Data.B = b;
Data.C = c;
}
}
}
Очевидно, что синхронизация потоков явно выполняется.
Однако я есть вопрос, основанный на следующих наблюдениях, согласно моему пониманию:
- Оператор
lock
гарантирует, что а) только один поток может получить доступ к Data
и б) поля в Data
никогда не будут быть "порванным". Lock
обеспечивает барьер памяти, который, насколько я вижу, не окажет заметного эффекта в этих двух контекстах. - Поскольку поля не отмечены
volatile
и поскольку нет операций Volatile.Read()
и Volatile.Write()
, три поля будут записаны в кэш, а не напрямую в основную память. - Единственный способ записи непосредственно в основную память - через один из вышеупомянутых «энергозависимых» механизмов, так как они используют операции
ref
и отключают оптимизацию, что приводит к чтению / записи в основной памяти. - Глядя на код, процессор при некотором или неизвестно мне, запишите эти поля в основную память.
- Я не понимаю, почему несколько потоков гарантированно увидят последнюю версию трех полей, особенно в слабо упорядоченной архитектуре памяти, такой как ARM.
Мой вопрос: как я могу быть уверен, что вызов ReadCopy()
после вызова Set()
увидит последние значения для трех полей? Вызывающий поток может находиться на другом ядре и иметь свои собственные кэшированные копии Data
.
Очевидно, что существуют «изменчивые» механизмы. Пример обычно вращается вокруг доступа к незаблокированным сегментам памяти. Но что из примера здесь? Я никогда не видел код, который использует lock
, а использует изменчивый механизм.