sync/atomic делает отдельную операцию неделимой. Это полезно для счётчиков и флагов, но не превращает любую последовательность действий в транзакцию.

Когда атомик проще мьютекса

var requests atomic.Int64
requests.Add(1)

var stopped atomic.Bool
stopped.Store(true)

Атомик подходит для:

  • счётчика запросов или ошибок;
  • флага остановки;
  • выдачи следующего ID;
  • чтения текущего значения без составной логики.

Мьютекс нужен, если проверка и изменение зависят от состояния:

mu.Lock()
if balance >= price {
    balance -= price
}
mu.Unlock()

Вариант if balance >= price { balance -= price } через Load и Add содержит гонку: между ними другой списатель изменит баланс.

CompareAndSwap

CAS записывает новое значение только если старое не изменилось:

for {
    current := balance.Load()
    if current < price {
        return ErrInsufficientFunds
    }
    if balance.CompareAndSwap(current, current-price) {
        return nil
    }
}

Здесь проверка и обновление атомарны. При неудаче цикл читает состояние заново. Такой код часто сложнее мьютекса и подвержен contention: на горячем значении мьютекс может быть быстрее и понятнее.

CAS полезен для небольшой конечной машины состояний и lock-free счётчиков, но требует тестов на конкурентные изменения.

Два значения снова не одна операция

type Account struct {
    balance atomic.Int64
    frozen  atomic.Bool
}

func (a *Account) Withdraw(amount int64) error {
    if a.frozen.Load() {
        return ErrFrozen
    }
    a.balance.Add(-amount)
    return nil
}

frozen и balance атомарны по отдельности, но Withdraw не атомарен целиком: заморозка может появиться между проверкой и списанием. Связанные поля меняют под одним мьютексом или упаковывают в один объект состояния.

Проблема ABA

CAS видит не версию объекта, а текущее значение. Последовательность A -> B -> A для него неотличима от «ничего не изменилось». Если меняется владелец, права или состояние объекта, важна не просто сумма, а версия.

Решения:

  • хранить value + version и сравнивать обе части;
  • пересоздавать состояние и менять указатель на него;
  • использовать мьютекс, если lock-free сложность не оправдана.

ABA не бывает у простого монотонного счётчика, потому что он не возвращается к старому значению.

Выравнивание и копирование

64-битные атомарные операции требуют правильного выравнивания адреса, особенно на 32-битных архитектурах. Поле атомарной переменной лучше ставить первым в структуре или использовать выделенную переменную.

После первого использования структуру с атомарной переменной не копируют. Значение noCopy в исходниках atomic напоминает об этом, а go vet находит часть таких копирований.

Коротко

  • Атомик заменяет мьютекс для простого значения, не для бизнес-операции.
  • CAS проверяет и меняет значение атомарно, но требует цикла и тестов на contention.
  • Несколько атомарных полей не образуют атомарное состояние.
  • ABA решают версией состояния или мьютексом.
  • Атомарные поля не копируют после первого использования.