Атомарность: операция целиком или никак
Атомарность
Атомарность — свойство операции, при котором для наблюдателя она выглядит неделимой: либо произошло всё предусмотренное изменение, либо не произошло ничего. В многопоточной программе это также означает, что конкурентные операции не могут вклиниться в середину защищённого действия.
История
Представление об атомарной операции появилось вместе с развитием вычислительных систем, где несколько процессов могли одновременно работать с общей памятью. Уже ранние многопроцессорные компьютеры требовали согласовывать доступ к общим данным: простая команда программы могла состоять из нескольких машинных шагов, а другой процессор — изменить данные между ними. Поэтому производители процессоров добавляли специальные атомарные инструкции, а операционные системы и языки программирования развивали средства синхронизации.
Параллельно атомарность стала важным свойством транзакций баз данных. В 1970-х годах Джим Грей систематизировал основы транзакционной обработки; в 1983 году он вместе с Андреасом Ройтером опубликовал работу о принципах проектирования систем обработки транзакций. Отсюда выросла известная группа свойств ACID: atomicity, consistency, isolation, durability — атомарность, согласованность, изоляция и долговечность.
В современных языках, включая C++, Java, Rust и Python, способы получить атомарное поведение различаются. Стандарт C++11 (2011) формализовал модель памяти и библиотеку атомарных типов; Java предоставляет классы java.util.concurrent.atomic; Rust включает атомарные типы в стандартную библиотеку. Важно не смешивать: атомарность транзакции базы, атомарная операция процессора и атомарность бизнес-действия — родственные идеи, но разные гарантии.
Что это такое
Представь, что в общем счётчике хранится число 10, а два потока одновременно хотят прибавить по единице. Наивная операция counter += 1 часто состоит из трёх шагов: прочитать число, вычислить новое значение, записать его. Оба потока могут прочитать 10, оба вычислить 11, а затем записать 11. Итог — 11 вместо 12. Каждый поток выполнил свою команду, но совокупное изменение потеряло обновление.
Атомарное увеличение не позволяет разделить действие на наблюдаемые части. Система гарантирует, что конкурирующие вызовы будут упорядочены так, будто сначала целиком выполнился один, потом другой. Тогда результатом станет 12. Это пример атомарности относительно конкретной переменной и конкретной операции; он не обещает, что целая последовательность из десятка команд автоматически станет неделимой.
В базах данных атомарность транзакции отвечает на другой вопрос: если перевод состоит из списания со счёта A и зачисления на счёт B, не останется ли система после сбоя в состоянии «списали, но не зачислили»? Транзакция группирует изменения: фиксация делает их видимыми как единый результат, а откат отменяет незавершённую группу.
Атомарность vs изоляция. Атомарность — «всё или ничего» для изменения. Изоляция — насколько параллельные транзакции скрыты друг от друга и какие результаты их взаимодействия допустимы. Транзакция может быть атомарной, но при недостаточной изоляции всё равно столкнуться с аномалиями чтения.
Атомарность vs долговечность. Успешно зафиксированная операция атомарна, если она не оставляет частичного состояния. Долговечность отвечает, переживёт ли зафиксированный результат отключение питания или перезапуск. Это отдельные гарантии.
Аналогии из жизни
Банковский перевод. Перевести сто рублей — значит одновременно отразить списание и зачисление. Аналогия хорошо показывает, почему половина операции неприемлема. Где ломается: техническая транзакция обычно охватывает только одну базу или систему. Между двумя независимыми банками могут использоваться отдельные подтверждения, компенсации и задержки; мгновенного общего «переключателя» может не быть.
Дверь с защёлкой. Пока дверь открыта или закрыта, наблюдатель не должен видеть её в невозможном промежуточном состоянии «наполовину заперта». Это напоминает неделимое переключение состояния. Где ломается: многие реальные физические механизмы как раз проходят через промежуточные положения; аналогия описывает желаемое наблюдаемое поведение, а не устройство программной блокировки.
Пакет документов. Сотрудник принимает папку только после проверки всех обязательных листов; иначе возвращает её целиком. Это похоже на фиксацию всей транзакции или её отмену. Где ломается: база данных может на время транзакции хранить внутренние промежуточные изменения. Важно, что конкурирующие наблюдатели не видят их как уже завершённый результат, а не то, что промежуточных вычислений физически нет.
Как это работает
Для атомарности есть несколько распространённых механизмов. На уровне процессора существуют специальные инструкции, например compare-and-swap (сравнить и заменить): «замени значение, только если оно всё ещё равно ожидаемому». Процессор выполняет проверку и замену как одну атомарную операцию. Если значение изменилось, программа повторяет попытку с новым наблюдаемым состоянием.
Упрощённо это выглядит так:
повторять:
старое = прочитать значение
новое = вычислить(старое)
пока compare_and_swap(значение, старое, новое) неуспешен
Для составных действий часто используют mutex (мьютекс, взаимное исключение) или другой lock (блокировку). Поток захватывает блокировку перед критической секцией — участком, который работает с общим состоянием, — выполняет все связанные шаги и освобождает её. Остальные участники ждут. Важно защищать одной и той же блокировкой весь цикл «прочитать → проверить → изменить → сохранить», а не отдельные строки по отдельности. Иначе между проверкой и изменением может вклиниться другой поток.
В базе данных транзакция начинается, выполняет набор запросов, а затем вызывает COMMIT (зафиксировать) или ROLLBACK (откатить). СУБД может вести журнал изменений, хранить версии страниц или использовать другие механизмы восстановления. При сбое она определяет, какие транзакции успели зафиксироваться, и приводит хранилище к допустимому состоянию. Конкретные гарантии зависят от базы и настроек: например, «успех» может означать разное с точки зрения подтверждения записи на физический носитель.
У атомарности есть границы. Операция над одним атомарным числом не делает автоматически атомарными два разных числа. Блокировка помогает лишь тогда, когда все участники соглашения её соблюдают. Транзакция одной базы обычно не включает автоматически внешний HTTP-запрос, отправку письма или запись в другую независимую базу. Для распределённых процессов приходится проектировать протоколы согласования, повторов и компенсаций.
Где встречается в обычной жизни
- При переводе денег приложение должно показать согласованный результат, а не частичное списание.
- При сохранении документа после сбоя система старается оставить предыдущую версию или полностью сохранённую новую.
- При загрузке файла временная копия может переименовываться в окончательную только после полной записи, чтобы читатель не получил обрывок.
- При покупке в интернет-магазине заказ и изменение остатка товара должны обрабатываться согласованно, хотя система может использовать несколько отдельных сервисов.
Где встречается в IT и бизнесе
- Транзакции базы данных: нужно менять несколько строк так, чтобы сбой не оставил несогласованную половину.
- Общие счётчики и очереди: нужно исключить потерю обновлений при параллельной работе потоков.
- Настройки и файлы: нужно заменить конфигурацию целиком, не оставляя читателю частично записанный файл.
- Операции API: нужно безопасно повторять запрос после тайм-аута, когда клиент не знает, успела ли первая попытка завершиться. Для этого часто проектируют идемпотентность, но она не тождественна атомарности.
- Несколько сервисов: нужно согласовать заказ, оплату и доставку. Здесь одной локальной транзакции часто недостаточно; применяют саги, события и компенсирующие действия.
Кто пользуется
Атомарные операции есть в процессорах и стандартных библиотеках популярных языков, поэтому ими пользуются практически все разработчики многопоточных систем. Транзакции с атомарными гарантиями поддерживают, например, PostgreSQL, MySQL/InnoDB и SQLite. Это не отдельный продукт с определённым числом пользователей, а базовое свойство вычислений и хранения данных; достоверную единую цифру масштаба для него назвать нельзя.
Альтернативы и конкуренты
- Mutex / блокировка. Плюс: понятна и подходит для группы связанных изменений. Минус: ошибки порядка захвата могут привести к взаимной блокировке, а длительное удержание снижает параллелизм.
- Атомарные инструкции без блокировки. Плюс: эффективны для простых операций и не требуют ожидания mutex в обычном случае. Минус: сложные алгоритмы труднее проектировать и проверять; «lock-free» не значит автоматически быстрее.
- Неизменяемые данные и передача сообщений. Плюс: меньше общего изменяемого состояния, следовательно, меньше гонок. Минус: копирование или организация обмена могут усложнить архитектуру и увеличить задержки.
- Оптимистическая версия с проверкой. Плюс: участники работают параллельно, конфликт обнаруживается при фиксации. Минус: при частых конфликтах приходится повторять работу; внешние побочные эффекты нельзя бездумно выполнять до проверки.
Когда НЕ стоит использовать
- Не пытайся объединить в одну атомарную операцию весь долгий процесс, который затрагивает независимые сервисы: общей блокировки или транзакции там может не быть. Нужен явный сценарий восстановления.
- Не добавляй блокировку к данным, которыми владеет только один поток: синхронизация усложнит код без пользы.
- Не считай атомарность заменой валидации и бизнес-правил. Неделимо выполненная операция всё равно может быть логически неправильной.
Связанные понятия
- Транзакция — группа изменений, фиксируемая или отменяемая как единое целое.
- Изоляция — правила видимости параллельных операций друг для друга.
- ACID — четыре свойства транзакционной обработки: атомарность, согласованность, изоляция и долговечность.
- Гонка данных — ситуация, где результат зависит от неконтролируемого порядка конкурентных обращений.
- Mutex — механизм, позволяющий одному участнику за раз входить в защищённый участок.
- Идемпотентность — свойство повторного выполнения запроса не менять итог после первого успешного применения.
Литература и источники
- Джим Грей и Андреас Ройтер, Transaction Processing: Concepts and Techniques, 1992 (английский) — систематический учебник по транзакционным системам.
- Джим Грей, «The Transaction Concept: Virtues and Limitations», 1981 (английский) — классическая статья о транзакциях; искать по названию.
- Документация PostgreSQL, раздел «Transactions» —
https://www.postgresql.org/docs/current/tutorial-transactions.html. - Документация C++,
std::atomic—https://en.cppreference.com/w/cpp/atomic/atomic. - Документация Python,
threadingиLock—https://docs.python.org/3/library/threading.html.
Где встретилось у меня
При разборе параллельной обработки и сохранения общего состояния в программном компоненте обсуждалось, как не допустить гонки между чтением, проверкой и записью данных. Атомарность всплыла как требование выполнять такой цикл целиком, а не защищать отдельные шаги.
Краткое резюме
- Атомарная операция наблюдается как целое: завершилась полностью или не оставила результата.
- Атомарность счётчика, блокировки и транзакции базы — связанные, но не одинаковые уровни гарантии.
- Защищай весь цикл работы с общим состоянием, а не только отдельные команды.
- Локальная атомарность не распространяется автоматически на внешние сервисы и побочные эффекты.
- Атомарность не заменяет изоляцию, долговечность, идемпотентность и проверку бизнес-правил.