Безопасно ли смешивать блокировки и операции блокировки?
У меня есть некоторый код, который должен быть потокобезопасным, поведение которого напоминает это:
protected long m_RunningValue
protected long m_RunningCounter
protected object m_Lock = new object();
public long RunningValue { get { return Interlocked.Read(m_RunningValue); } }
public long RunningCounter { get { return Interlocked.Read(m_RunningCounter); } }
public void DoCalculation(int newValue, int newQuantity)
{
lock(m_Lock)
{
Interlocked.Add(ref m_RunningValueA, newValue);
Interlocked.Add(ref m_RunningCounter, newQuantity);
if(Interlocked.Read(ref newQuantity) == 0)
{
...m_RunningValue gets further modified here
}
}
}
Вычисление должно блокировать как значение, так и счетчик, иначе условие гонки может повлиять на блок if (...), однако их вообще не нужно синхронизировать при считывании, т.е. если счетчик и значение изменяются между попытками прочитайте оба, это на 100% нормально со мной.
Блокировка при чтении предназначена для чтения 64-битного значения для обеспечения безопасности потока.
Безопасно ли смешивать блокировки и блокировки? Я читал на других веб-страницах, что смешивать их небезопасно, однако я не могу найти разъяснения, если это означает, что смешивание их - отличный способ внести незначительные ошибки или если на системном уровне это может повредить соответствующие структуры данных.
Является ли стоимость всей этой блокировки (64-разрядная среда выполнения.NET 4.0) полностью разрушающей цель сохранения блокировки ReaderWriterSlim вокруг свойства get()?
1 ответ
Что касается отредактированной версии, это не гарантирует многопоточность.
Рассмотрим 32-битную платформу. Это означает, что 64-битные длинные значения должны быть доступны в двух отдельных операциях.
Вполне возможно, что один поток прочитает первую половину числа, затем поменяет местами процессор, а вторая половина значения изменится за его спиной. Теперь этот поток имеет совершенно недопустимое значение.
Документы для Interlocked.Read прямо указывают, что:
Метод Read и 64-разрядные перегрузки методов Increment, Decrement и Add действительно атомарны только в системах, где System.IntPtr имеет длину 64 бита. В других системах эти методы являются атомарными по отношению друг к другу, но не по отношению к другим средствам доступа к данным. Таким образом, для обеспечения безопасности потоков в 32-разрядных системах любой доступ к 64-разрядному значению должен осуществляться через члены класса Interlocked.
(акцент мой)
РЕДАКТИРОВАТЬ: Теперь, когда это было отредактировано назад, это выглядит немного глупо, но я оставлю это здесь, чтобы подчеркнуть, что если вы соединяетесь в некоторых местах, но не в других, это отчасти бессмысленно.