Использует ли Interlocked.CompareExchange барьер памяти?

17

Я читаю сообщение Джо Даффи о Неустойчивые чтения и записи и своевременность , и я пытаюсь понять что-то о последнем примере кода в сообщении:

while (Interlocked.CompareExchange(ref m_state, 1, 0) != 0) ;
m_state = 0;
while (Interlocked.CompareExchange(ref m_state, 1, 0) != 0) ;
m_state = 0;
… 

Когда выполняется вторая операция CMPXCHG, использует ли она барьер памяти, чтобы гарантировать, что значение m_state действительно является последним значением, написанным для него? Или он просто использует некоторое значение, которое уже хранится в кеше процессора? (предполагая, что m_state не объявляется изменчивым).
Если я правильно понимаю, если CMPXCHG не будет использовать барьер памяти, тогда вся процедура обнаружения блокировки не будет честной, так как очень вероятно, что поток, который первым приобрел блокировку, будет тем, который будет приобретать < strong> все следующие блокировки . Правильно ли я понял, или я что-то пропустил здесь?

Изменить . Главный вопрос: действительно ли вызов CompareExchange вызывает барьер памяти перед попыткой прочитать значение m_state. Таким образом, независимо от того, будет ли назначение 0 видимым для всех потоков, когда они снова попытаются вызвать CompareExchange.

    
задан Abel 17.10.2009 в 10:15
источник

6 ответов

22

Любая инструкция x86 с префиксом блокировка имеет полный барьер памяти . Как показал ответ Абеля, Interlocked * API и CompareExchanges используют блокировку -предоставляемую команду, такую ​​как lock cmpxchg . Таким образом, это подразумевает забор памяти.

Да, Interlocked.CompareExchange использует барьер памяти.

Почему? Потому что процессоры x86 сделали это. Из раздела Intel 3a: Руководство по системному программированию, часть 1 , раздел 7.1.2.2:

  

Для процессоров семейства P6 заблокированные операции сериализуют все выдающиеся операции загрузки и хранения (то есть ждут их завершения). Это правило справедливо и для процессоров Pentium 4 и Intel Xeon, за одним исключением. Операции загрузки, которые ссылаются на слабо упорядоченные типы памяти (например, тип памяти WC), не могут быть сериализованы.

volatile не имеет ничего общего с этим обсуждением. Речь идет об атомных операциях; для поддержки атомных операций в CPU, x86 гарантирует, что все предыдущие загрузки и хранилища будут завершены.

    
ответ дан minjang 11.11.2009 в 17:57
источник
10

ref не соблюдает обычные правила volatile , особенно в таких вещах, как:

volatile bool myField;
...
RunMethod(ref myField);
...
void RunMethod(ref bool isDone) {
    while(!isDone) {} // silly example
}

Здесь RunMethod не гарантирует обнаружение внешних изменений в isDone , хотя базовое поле ( myField ) равно volatile ; RunMethod не знает об этом, поэтому не имеет правильного кода.

Однако! Это должно быть не проблема:

  • , если вы используете Interlocked , затем используйте Interlocked для доступа all к
  • , если вы используете lock , затем используйте lock для доступа all к

Следуйте этим правилам, и он должен работать нормально.

Повторить редактирование; да, поведение является важной частью Interlocked . Честно говоря, я не знаю, как это реализовано (барьер памяти и т. Д.), Обратите внимание, что это методы «InternalCall», поэтому я не могу проверить; -p), но да: обновления из одного потока будут немедленно видны все остальные , пока используют методы Interlocked (следовательно, моя точка выше).

    
ответ дан Marc Gravell 17.10.2009 в 10:23
источник
5

Похоже, что существует некоторое сравнение с функциями Win32 API с тем же именем, но этот поток относится ко всему классу C # Interlocked . Из его самого описания гарантировано, что его операции являются атомарными. Я не уверен, как это переводится как «полные барьеры памяти», как упомянуто в других ответах здесь, но судите сами.

В однопроцессорных системах ничего особенного не происходит, есть только одна инструкция:

FASTCALL_FUNC CompareExchangeUP,12
        _ASSERT_ALIGNED_4_X86 ecx
        mov     eax, [esp+4]    ; Comparand
        cmpxchg [ecx], edx
        retn    4               ; result in EAX
FASTCALL_ENDFUNC CompareExchangeUP

Но в многопроцессорных системах аппаратная блокировка используется для предотвращения одновременного доступа к другим ядрам:

FASTCALL_FUNC CompareExchangeMP,12
        _ASSERT_ALIGNED_4_X86 ecx
        mov     eax, [esp+4]    ; Comparand
  lock  cmpxchg [ecx], edx
        retn    4               ; result in EAX
FASTCALL_ENDFUNC CompareExchangeMP

Интересно прочитать здесь и там некоторые неправильные выводы, но все-все-таки отлично по теме это в блоге на CompareExchange .

    
ответ дан Abel 10.11.2009 в 01:18
источник
2

Блокированные функции гарантируют остановку шины и процессора, когда они решают операнды. Непосредственным следствием этого является то, что ни один переключатель нитей на вашем процессоре или другой не будет прерывать функцию блокировки в середине ее выполнения.

Поскольку вы передаете ссылку на функцию c #, базовый код ассемблера будет работать с адресом фактического целого числа, поэтому доступ к переменной не будет оптимизирован. Он будет работать точно так, как ожидалось.

edit: Вот ссылка, которая лучше объясняет поведение инструкции asm: Ссылка
Как вы можете видеть, шина останавливается, заставляя цикл записи, поэтому любые другие «потоки» (читай: другие ядра процессора), которые будут пытаться использовать шину в одно и то же время, будут помещены в очередь ожидания.

    
ответ дан Blindy 17.10.2009 в 11:15
источник
2

MSDN рассказывает о функциях API Win32: " Большинство блокированных функций обеспечивают полные барьеры памяти на всех платформах Windows "

(исключения - это блокированные функции с явной семантикой получения / выпуска)

Из этого я бы пришел к выводу, что Interlocked времени выполнения C # обеспечивает те же гарантии, что и документированные с другим идентичным поведением (и они разрешают внутренние операторы ЦП на платформах, которые я знаю). К сожалению, с тенденцией MSDN устанавливать образцы вместо документации, это явно не указано.

    
ответ дан peterchen 17.10.2009 в 18:33
источник
0

Согласно ECMA-335 (раздел I.12.6.5):

  

5.   Явные атомные операции.     Библиотека классов обеспечивает множество атомных операций   в   System.Threading.Interlocked    класс. Эти операции (например, Increment,   Decrement, Exchange и CompareExchange) выполнять неявное получение / выпуск   операции .

Итак, эти операции следуют принципу наименьшего удивления .

    
ответ дан Valery Petrov 27.07.2017 в 11:37
источник
user contributions licensed under cc by-sa 3.0 with attribution required. DMCA.com Protection Status