Наблюдает ли Monitor.Wait, что поля перечитываются?

Общепризнанно (я верю!), что lock будет принудительно перезагружать любые значения из полей (по существу действуя как барьер или ограждение памяти), моя терминология в этой области немного ослабляется, я боюсь), вследствие чего поля, которые только когда-либо доступны внутри lock, сами по себе не должны быть volatile.

(Если я уже не прав, просто скажите!)

Хороший комментарий был здесь поднят, спрашивая, верно ли то же самое, если код выполняет Wait() - то есть, когда он был Pulse() d, будет ли он перезагружать поля из памяти или они могут находиться в регистре (и т.д.).

Или проще: нужно ли поле volatile, чтобы убедиться, что текущее значение получено при возобновлении после 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 каждый раз, когда он пульсирует, но я заинтригован, знаю ли он, что необходимо.

Ответ 1

Так как метод Wait() освобождает и перезаписывает блокировку Monitor, если lock выполняет семантику забора памяти, тогда Monitor.Wait() будет также.

Надеемся, что вы ответите на ваш комментарий:

Поведение блокировки Monitor.Wait() находится в документах (http://msdn.microsoft.com/en-us/library/aa332339.aspx), добавлено выделение:

Когда поток вызывает Wait, он освобождает блокировку объекта и входит в очередь ожидания объекта. Следующий поток в очереди готовности объекта (если он есть) получает блокировку и имеет эксклюзивное использование объекта. Все потоки, которые вызывают Wait, остаются в очереди ожидания, пока не получат сигнал от Pulse или PulseAll, отправленный владельцем блокировки. Если отправлено Pulse, влияет только поток в начале очереди ожидания. Если отправлено PulseAll, все потоки, ожидающие этого объекта, будут затронуты. Когда сигнал принят, один или несколько потоков покидают очередь ожидания и входят в очередь готовности. Поток в готовой очереди разрешен для повторной блокировки.

Этот метод возвращает, когда вызывающий поток повторно блокирует объект.

Если вы спрашиваете о том, ссылается ли lock/приобретенный Monitor на барьер памяти, спецификация ECMA CLI говорит следующее:

12.6.5 Замки и потоки:

Приобретение блокировки (System.Threading.Monitor.Enter или ввод синхронизированного метода) неявно выполняет операцию волатильной операции чтения и освобождение блокировки (System.Threading.Monitor.Exit или оставление синхронизированного метода) неявно выполняет операцию волатильной записи. См. П. 12.6.7.

12.6.7 Неустойчивые чтения и записи:

Волатильное чтение имеет "приобретать семантику", что означает, что чтение будет гарантировано до любых ссылок на память, которые происходят после команды чтения в последовательности команд CIL. У volatile write есть "семантика релиза", что означает, что запись гарантирована после каждой записи памяти до команды записи в последовательности команд CIL.

Кроме того, эти записи в блоге содержат некоторые детали, которые могут представлять интерес:

Ответ 2

В ответ на ответ Майкла Бэрра не только Wait освобождает и повторно получает блокировку, но делает это так, что другой поток может вывести блокировку, чтобы проверить состояние совместного доступа и вызвать Pulse. Если второй поток не вынимает блокировку, тогда Pulse будет бросать. Если они не Pulse, первый поток Wait не вернется. Следовательно, любой другой доступ к потоку для общего состояния должен происходить в рамках надлежащего сценария, защищенного памятью.

Таким образом, предполагая, что методы Monitor используются в соответствии с локально контролируемыми правилами, все обращения к памяти происходят внутри блокировки, и, следовательно, только поддержка автоматического барьера памяти lock является релевантной/необходимой.

Ответ 3

Возможно, я могу помочь вам на этот раз... вместо использования 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 не... Я надеюсь, что что-то стоит (но вы, наверное, уже об этом подумали).