Я знаю, что могу создать неизменяемый (то есть потокобезопасный) объект, подобный этому:
class CantChangeThis
{
private readonly int value;
public CantChangeThis(int value)
{
this.value = value;
}
public int Value { get { return this.value; } }
}
Однако я обычно "обманываю" и делаю это:
class CantChangeThis
{
public CantChangeThis(int value)
{
this.Value = value;
}
public int Value { get; private set; }
}
Тогда мне стало интересно: "Почему это работает?" Это действительно потокобезопасно? Если я использую его так:
var instance = new CantChangeThis(5);
ThreadPool.QueueUserWorkItem(() => doStuff(instance));
Тогда то, что он действительно делает (я думаю):
- Выделение пространства в общей папке для экземпляра
- Инициализация значения внутри экземпляра в куче
- Запись указателя/ссылки на это пространство в локальную переменную (конкретный стек)
- Передача ссылки на этот поток как значение. (Интересно, как я это написал, ссылка находится внутри закрытия, что делает то же самое, что и мой экземпляр, но пусть игнорирует это.)
- Тема переходит в кучу и считывает данные из экземпляра.
Однако значение этого экземпляра сохраняется в общей памяти. Эти два потока могут иметь несовместимые с кэшем представления этой памяти в куче. Что это значит, что поток threadpool фактически видит построенный экземпляр, а не некоторые данные мусора? Есть ли неявный барьер памяти в конце любой конструкции объекта?