Это является общепринятым (я верю!) это a lock
вынудит любые значения поля быть перезагруженными (по существу действующий как барьер памяти или забор - моя терминология в этой области становится немного свободной, я боюсь), с последствием, что поля, к которым только когда-либо получают доступ внутри a lock
не должны самостоятельно быть volatile
.
(Если я уже неправ, просто говорю!)
Хороший комментарий был повышен здесь, подвергнув сомнению, верно ли то же, если код делает a Wait()
- т.е. после того как это было Pulse()
d, будет он перезагружать поля из памяти, или могли они быть в регистре (и т.д.).
Или проще: делает поле, должен быть volatile
гарантировать, что текущее значение получено при возобновлении после a Wait()
?
Рассмотрение отражателя, Wait
раскритиковывает в ObjWait
, который является managed internalcall
(то же как Enter
).
Рассматриваемый сценарий был:
bool closing;
public bool TryDequeue(out T value) {
lock (queue) { // arbitrary lock-object (a private readonly ref-type)
while (queue.Count == 0) {
if (closing) { // <==== (2) access field here
value = default(T);
return false;
}
Monitor.Wait(queue); // <==== (1) waits here
}
...blah do something with the head of the queue
}
}
Очевидно, я мог просто сделать его volatile
, или я мог выгнать это с квартиры так, чтобы я вышел и повторно ввел Monitor
каждый раз это пульсируется, но я заинтригован знать, необходим ли любой.
Поскольку метод Wait ()
освобождает и повторно устанавливает блокировку Monitor
, если lock
выполняет семантику забора памяти, затем Monitor.Wait ()
также будет.
Надеюсь ответить на ваш комментарий:
Поведение при блокировке Monitor.Wait ()
находится в документации ( http://msdn.microsoft.com/en-us/library /aa332339.aspx), курсив добавлен:
Когда поток вызывает Wait, он снимает блокировку с объекта и входит в очередь ожидания объекта. Следующий поток в очереди готовности объекта (если таковая имеется) получает блокировку и эксклюзивно использует объект. Все потоки, вызывающие
Wait
, остаются в очереди ожидания до тех пор, пока не получат сигнал от Pulse илиPulseAll
, отправленный владельцем блокировки. Если отправляетсяИмпульс
, затрагивается только поток в начале очереди ожидания. Если отправленоPulseAll
, затронуты все потоки, ожидающие объекта. Когда сигнал получен, один или несколько потоков покидают очередь ожидания и входят в очередь готовности.Потоку в очереди готовности разрешено повторно получить блокировку.Этот метод возвращается, когда вызывающий поток повторно захватывает блокировку объекта .
Если вы спрашиваете о ссылке на то, подразумевает ли блокировка
/ полученный монитор
барьер памяти, в спецификации ECMA CLI говорится следующее:
12.6.5 Блокировки и потоки:
Получение блокировки (
System.Threading.Monitor.Enter
или ввод синхронизированного метода) должно неявно выполнять операцию непостоянного чтения и снимать блокировку (] System.Threading.Monitor.Exit
или выход из синхронизированного метода) должен неявно выполнять операцию энергозависимой записи. См. §12.6.7.
12.6.7 Энергозависимое чтение и запись:
Энергозависимое чтение имеет «семантику получения», означающую, что чтение гарантированно произойдет до любых обращений к памяти, которые происходят после инструкции чтения в последовательности инструкций CIL. Энергозависимая запись имеет "семантику освобождения", означающую, что запись гарантированно произойдет после любых обращений к памяти до инструкции записи в последовательности инструкций CIL.
Кроме того, в этих записях блога есть некоторые подробности, которые могут быть интересны:
В дополнение к ответу Майкла Берра, Ожидание
не только освобождает и повторно устанавливает блокировку, но и делает это так, чтобы другой поток может снять блокировку, чтобы проверить общее состояние и вызвать Pulse
. Если второй поток не снимает блокировку, то Pulse
выбросит. Если они этого не сделают, Pulse
Wait
первого потока не вернется.Следовательно, доступ любого другого потока к общему состоянию должен происходить в рамках правильного сценария без памяти.
Итак, если предположить, что методы Monitor
используются в соответствии с правилами локальной проверки, тогда все обращения к памяти происходят внутри блокировки, и, следовательно, только поддержка автоматического барьера памяти блокировки
актуально / необходимо.
Возможно, я смогу помочь вам на этот раз... вместо volatile
вы можете использовать Interlocked.Exchange
с целым числом.
if (closing==1) { // <==== (2) access field here
value = default(T);
return false;
}
// somewhere else in your code:
Interlocked.Exchange(ref closing, 1);
Interlocked.Exchange
- это механизм синхронизации, volatile
- нет... Надеюсь, это чего-то стоит (но вы, вероятно, уже думали об этом).