Concurrency, Synchronization and Consistency. Пост №7. Барьеры памяти.
Сегодня рассмотрим еще одну функциональность CPU позволяющую избегать несогласованности. Речь пойдет о барьерах памяти.
Барьеры памяти используются для обеспечения правильного порядка обращения памяти , предотвращая переупорядочивание операций с памятью со стороны CPU. В многоядерных системах процессоры часто переупорядочивают инструкции для оптимизации. Однако это может привести к несогласованности, когда несколько ядер CPU обращаются к одним и тем же данным. Без правильного порядка памяти один CPU может обнаружить устаревшие данные даже после снятия блокировки, что приведет к состоянию гонки.
Довольно подробно о том, почему происходят переупорядочивания на разных архитектурах CPU разбирал ранее в предыдущих постах про модели памяти, обязательно посмотрите чтобы лучше усвоилось:
- Что такое модель памяти? Sequential Consistency.
- Модель памяти X86
- Модель памяти ARM
- Выводы по моделям памяти + материалы
И так, барьеры памяти. Что же они нам дают? Барьеры памяти гарантируют, что:
- Все записи внутри критической секции полностью завершаются до снятия блокировки.
- Все операции чтения после получения блокировки видят последнюю версию общих данных.
Как устроен этот механизм под капотом в двух словах?
В постах про модели памяти я упоминал подсистему CPU для оптимизации операций записи - очередь записи. Главное что нужно знать в контексте поста что это асинхронная очередь.
Недавно мы увидели наличие еще одной очереди - очередь когерентности / инвалидации. Она принимает и отправляет другим ядрам сообщения о необходимости обновить свои кеши. Тоже асинхронная.
Барьер памяти это буквально предохранитель, который влияет в зависимости от ситуации может:
- Принудительно заставить ядро сбросить все записи из своей очереди записи в кеш, чтобы инициировать работу протокола когерентности.
- Заставить процессор пройтись по очереди когерентности и проверить наличие сообщений на инвалидацию кеша связанных с той ячейкой которую требуется прочитать.
Итог
Теперь у нас есть и блокировки и барьеры памяти, как они работают вместе? Рассмотрим полный алгоритм:
1. Получение блокировки: Ядро 1 использует CAS для получения блокировки. Протокол когерентности гарантирует, что кэш ядра 1 получает исключительное право владения переменной блокировки.
2. Изменение данных в критической секции: Ядро 1 изменяет общие данные. В ходе этого процесса сообщения об иналидации отправляются в другие кэши, которые могут содержать копии общих данных.
3. Снятие блокировки + барьер памяти: Перед снятием блокировки ядро 1 устанавливает барьер памяти , чтобы гарантировать завершение всех операций записи и инвалидации. Барьер памяти гарантирует полное распространение всех сообщений когерентности до того, как блокировка станет доступной другим процессорам.
4. Другое ядро получает блокировку: Ядро 2 получает блокировку с помощью CAS. После получения блокировки барьер памяти гарантирует, что ядро 2 считывает последнюю версию общих данных, изменённых ядром 1, без устаревших данных.
Теперь у нас есть весь необходимый аппаратный фундамент чтобы разрабатывать корректные конкурентные программы😊
На этом первая часть постов завершена, по аппаратной части основные концепции разобрали, следующие посты будут уже ближе к языкам программирования и операционной системе.
Всем спасибо что читали, до встречи!
Post #399
1.31K
- 👍 9
- 🔥 6
- ❤ 2
- 🥰 1
- 👏 1
- 🤗 1