Как ответить
Атомарность — это свойство операции или набора операций, которые выполняются как единое неделимое целое. Если операция атомарна, то она либо выполняется полностью, либо не выполняется вовсе. Никакого промежуточного состояния не существует. В контексте concurrency это гарантирует, что один поток не увидит частичного результата другого потока.
На уровне процессора атомарность обеспечивается специальными инструкциями. Например, в x86 это инструкция CMPXCHG (compare-and-exchange) с префиксом LOCK, который блокирует шину памяти или кэш-линию на время выполнения. В Java для примитивных типов (кроме long и double) чтение и запись атомарны по спецификации JVM. Для long и double — нет, если они не объявлены как volatile. В C++ атомарность обеспечивается через std::atomic, который внутри использует те же CMPXCHG или барьеры памяти.
Главная проблема, которую решает атомарность, — это race condition. Допустим, два потока одновременно инкрементируют счётчик. Без атомарности операция counter++ разбивается на три шага: чтение, сложение, запись. Если потоки пересекутся, одно приращение потеряется. Атомарный инкремент (atomic_fetch_add или synchronized блок) гарантирует, что вся последовательность выполняется без прерывания.
Атомарность бывает на разных уровнях:
- Аппаратная — инструкции процессора (CAS, LL/SC).
- Языковая —
volatileв Java/C# илиstd::atomicв C++. - Транзакционная — STM (Software Transactional Memory), где набор операций выполняется как атомарная транзакция с откатом при конфликте.
- Базы данных — ACID-транзакции, где атомарность означает, что все изменения в рамках транзакции применяются или откатываются целиком.
На практике атомарность не бесплатна. Аппаратные блокировки (LOCK-префикс) сбрасывают конвейер процессора и замедляют соседние ядра. Поэтому в высокопроизводительном коде стараются минимизировать атомарные операции: используют локальные копии данных, lock-free структуры на CAS, или разделяют данные так, чтобы каждый поток работал со своей копией (thread-local storage).
Пример на C++:
#include <atomic>
#include <thread>
std::atomic<int> counter(0);
void increment() {
for (int i = 0; i < 1000; ++i) {
counter.fetch_add(1, std::memory_order_relaxed);
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
// counter == 2000 гарантированно
}Здесь fetch_add — атомарная операция. Даже если два потока выполняются одновременно, счётчик всегда будет корректен. Без std::atomic результат мог бы быть меньше 2000 из-за race condition.
Важно помнить: атомарность не то же самое, что потокобезопасность. Атомарная операция защищает только одну конкретную операцию. Если у вас есть несколько атомарных операций подряд, между ними может вмешаться другой поток. Для композиции атомарных действий нужны блокировки или транзакционная память.