Подтвердить что ты не робот

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

4b9b3361

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