Concurrency, Synchronization and Consistency. Пост №6. Блокировки. Атомарные операции на CPU.
Сегодня разберемся начнем разбираться с основной проблемой из-за которой при наличии всех рассмотренных ранее оптимизаций мы как программисты можем видеть недетерминированное поведение программы.
Вы должны были заметить по содержанию прошлых постов что процессор превратился из небольшой монолитной конструкуии в распределенную систему с микросервисами. А в распеределенной среде самая ненадежная вещь - это сеть. Она бывает медленной, могут случаться разрывы итп. Где же мы с этим сталкиваемся в CPU? Два поста назад была картинка с coherence controller. Его обязанность - принимать и отправлять сообщения когерентности (о необходимости инвалидировать кеш). И вот такая отправка не является атомарной. Пока сообщение идет до адресата он (адресат) может распоряжаться кешом как валидным и поток будет работать со stale data (читать и даже изменять данные). Выглядит это все как асинхронная коммуникация в которой единственная гарантия - eventual consistency.
Что же с этим можно сделать? Начнем с простого.
Locks, Compare-and-swap (CAS), Test-and-set (TAS)
Блокировки используются в многопоточных программах для защиты критических секций - мест в программе, где осуществляется доступ к общим данным и их изменение. Обеспечивая взаимное исключение (mutual exclusion, mutex), блокировки предотвращают одновременный доступ нескольких процессоров к критической секции и гарантируют, что только один процессор может изменять общие данные одновременно.
Блокировка с точки зрения кода - это просто переменная типа число с возможными значениями 0 (разблокирован) и 1 (разблокирован). Она не представляет особого интереса без отдельного набора операций над ней, а именно:
- Сравнение c обменом (CAS): сравнивает текущее значение ячейки памяти (в частности, переменной блокировки) с ожидаемым значением. Если они совпадают, CAS атомарно заменяет значение ячейки памяти новым.
- Test-And-Set (TAS): проверяет, равен ли участок памяти некоторому значению и есла да то заменяет его на новое атомарно.
Эти операции гарантируют, что только одно ядро может в момент времени может успешно изменить блокировку, предотвращая возникновение состояния гонки на уровне установки блокировки. Когда процессор устанавливает блокировку с помощью CAS или TAS, протокол когерентности обеспечивает процессору монопольный доступ к ячейке памяти (переменной блокировки).
Что такое состояние гонки разберу в отдельном посте подробно и с примерами, пока для простоты можно считать что это нежелательное состояние для нашей программы.
Выводы
Казалось бы, обеспечили эксклюзивный доступ к общим данным, всё ли на этом? Нет, так как эти атомарные операции гарантируют атомарную работу только с самой переменной блокировки, не с данными в критической секции. Мы повысили надежность нашей многопоточной программы, но все еще дела могут пойти скверно. Как это решать рассмотрим в следующем посте (Спойлер: мы очень близки к идеалу).
Спасибо что читали, задавайте вопросы в комментариях, ставьте реакции😊
Post #398
1.34K
- 🔥 12
- 👍 5
- ❤ 2
- 🥰 1
- 👏 1
- 🤔 1