Как оператор блокировки обеспечивает синхронизацию внутри процессора? - PullRequest
10 голосов
/ 13 мая 2011

У меня есть небольшое тестовое приложение, которое выполняет два потока одновременно.Один увеличивает static long _value, другой уменьшает его.С помощью ProcessThread.ProcessorAffinity я гарантировал, что потоки связаны с разными физическими (без HT) ядрами для обеспечения внутрипроцессорной связи, и я гарантировал, что они перекрываются во время выполнения в течение значительного промежутка времени.

ИзКонечно, следующее не приводит к нулю:

for (long i = 0; i < 10000000; i++)
{
    _value += offset;
}

Итак, логический вывод будет следующим:

for (long i = 0; i < 10000000; i++)
{
    Interlocked.Add(ref _value, offset);
}

Что, конечно, приводит к нулю.

Однако следующее также приводит к нулю:

for (long i = 0; i < 10000000; i++)
{
    lock (_syncRoot)
    {
        _value += offset;
    }
}

Конечно, оператор lock гарантирует, что операции чтения и записи не переупорядочиваются, поскольку он использует полный забор.Тем не менее, я не могу найти никакой информации, касающейся синхронизации процессорных кешей.Если бы не было никакой синхронизации кэша, я бы подумал, что должен увидеть отклонение от 0 после завершения обоих потоков?

Может кто-нибудь объяснить мне, как lock / Monitor.Enter/Exit обеспечиваетчто процессорные кэши (кэши L1 / L2) синхронизированы?

Ответы [ 3 ]

9 голосов
/ 13 мая 2011

Когерентность кэша в этом случае не зависит от lock.Если вы используете оператор lock, это гарантирует, что ваши команды ассемблера не будут смешаны.a += b не является атомарным для процессора, он выглядит следующим образом:

  • Загрузка данных в регистр из памяти
  • Увеличение данных
  • Сохранение данных обратно

И без блокировки это может быть:

  • Загрузка данных в регистр X из памяти
  • Загрузка данных в регистр Y из памяти
  • Приращение данных (в X)
  • Уменьшение данных (в Y)
  • Сохранение данных обратно (из X)
  • Сохранение данных обратно (из Y) // В этом случае приращение теряется.

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

Таким образом, lock не обеспечивает синхронизацию кэшей.Синхронизация кэша - это внутренняя функция процессора, которая не зависит от кода.Вы можете прочитать об этом здесь .

Когда одно ядро ​​записывает значение в память, а затем, когда второе ядро ​​пытается прочитать это значение, у него не будет фактической копии в кеше, если толькоего запись в кэше становится недействительной, поэтому происходит потеря кэша.И эта ошибка в кэше заставляет запись в кэше обновляться до фактического значения.

4 голосов
/ 13 мая 2011

Модель памяти CLR гарантирует (требует), чтобы грузы / хранилища не могли пересечь забор .Реализаторы CLR должны реализовать это на реальном оборудовании, , которое они делают .Однако это основано на объявленном / понятном поведении оборудования, которое может быть неправильным .

1 голос
/ 13 мая 2011

Ключевое слово lock является просто синтаксическим сахаром для пары вызовов System.Threading.Monitor.Enter() и System.Threading.Monitor.Exit().Реализации Monitor.Enter() и Monitor.Exit() устанавливают ограничение памяти, что влечет за собой выполнение соответствующей очистки памяти для архитектуры.Таким образом, ваш другой поток не продолжит работу, пока не увидит хранилища, полученные в результате выполнения заблокированного раздела.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...