Monitor.Wait гарантирует, что поля перечитываются? - PullRequest
22 голосов
/ 12 марта 2010

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

Ответы [ 3 ]

17 голосов
/ 12 марта 2010

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

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

4 голосов
/ 12 марта 2010

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

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

1 голос
/ 12 марта 2010

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

...