Я читал ответ на подобный вопрос, но я все еще немного смущен... У Abel был большой ответ, но это - часть, в которой я не уверен:
... объявление энергозависимой переменной делает это энергозависимым для каждого доступа. Невозможно вызвать это поведение, любой другой путь, следовательно энергозависимый, не может быть заменен Взаимно блокируемым. Это необходимо в сценариях, где другие библиотеки, интерфейсы или аппаратные средства могут получить доступ к Вашей переменной и обновить ее в любое время, или нуждаться в новой версии.
Делает Interlocked
видимость гарантии атомарной операции ко всем потокам, или делают я все еще должен использовать volatile
ключевое слово на значении для гарантии видимости изменения?
Вот мой пример:
volatile int value = 100000; // <-- do I need the volitile keyword
// ....
public void AnotherThreadMethod()
{
while(Interlocked.Decrement(ref value)>0)
{
// do something
}
}
public void AThreadMethod()
{
while(value > 0)
{
// do something
}
}
Обновление:
Я был плохим спортом, и я изменил исходный пример, таким образом, здесь это снова:
public class CountDownLatch
{
private volatile int m_remain; // <--- do I need the volatile keyword here?
private EventWaitHandle m_event;
public CountDownLatch(int count)
{
Reset(count);
}
public void Reset(int count)
{
if (count < 0)
throw new ArgumentOutOfRangeException();
m_remain = count;
m_event = new ManualResetEvent(false);
if (m_remain == 0)
{
m_event.Set();
}
}
public void Signal()
{
// The last thread to signal also sets the event.
if (Interlocked.Decrement(ref m_remain) == 0)
m_event.Set();
}
public void Wait()
{
m_event.WaitOne();
}
}
Их не * требуется ** волатильность, потому что вы никогда никогда не проверяете значение заблокированной переменной. Вместо этого вы всегда проверяете значение , возвращаемое заблокированной операцией (операциями). Смешивание взаимосвязанных операций и обычного присваивания / сравнения всегда приводит к неправильному коду.
Я не уверен, что такое функция Reset (), но этому фрагменту кода нет места в межпотоковом примитиве: вы присваиваете m_remain, вы напрямую проверяете значение m_remain, это очень плохо. Я настоятельно рекомендую вам убрать это: не только реализовано неправильно, но я очень сомневаюсь, что семантика «сброса» счетчика в середине срока службы необходима. Оставьте это просто: ctor (переместите в него код из Reset) Signal и Wait - единственные необходимые три оператора, и они верны, как и сейчас.
Обновлено После редактирования кода.
Игнорируя тот факт, что вы не должны смешивать эти два, если вы в конечном итоге смешаете их, то да, volatile все равно понадобится. Volatile в первую очередь касается кода IL и JIT-кода, сгенерированного, чтобы гарантировать, что значение всегда считывается из фактического места в памяти и не происходит оптимизации, такой как переупорядочение кода. Тот факт, что несвязанный фрагмент кода обновляет значение с помощью операций Interlocked, не влияет на другие части, считывающие значение.Без атрибута volatile
компилятор / JIT может по-прежнему генерировать код, который игнорирует записи, которые происходят где-то в другом месте, не имеет значения, если записи являются блокированными или прямым назначением.
Кстати, существуют допустимые шаблоны, которые смешивают обычные операции чтения и заблокированные операции, но они обычно включают Interlocked.CompareExchange и делают следующее: чтение текущего состояния, выполнение некоторых вычислений на основе текущего состояния, попытка заменить состояние как заблокированное сравнение -exchange: в случае успеха нормально, в противном случае отбросить результат вычисления и вернуться к шагу 1.
Я думаю, что System.Threading.Thread.VolatileRead(ref myVariable) может быть тем, что вы ищете. Используя его в сочетании с Interlocked.Increment, можно гарантировать, что изменения будут атомарными, а значения, которые вы считываете, будут самыми последними.