Для чего предназначен пакет `atomic` и когда его стоит использовать?

Ответ

Пакет atomic в Go предоставляет низкоуровневые атомарные операции, которые позволяют безопасно работать с общими данными из нескольких горутин без использования мьютексов.

Атомарная операция гарантированно выполняется целиком, без прерывания со стороны планировщика или других горутин, обеспечивая целостность данных на аппаратном уровне.

Когда использовать atomic:
Его стоит использовать для простых, коротких операций над примитивными типами, таких как:

  • Инкремент/декремент счетчиков.
  • Чтение/запись флагов или конфигурационных параметров.
  • Реализация lock-free алгоритмов.

Они являются высокопроизводительной альтернативой мьютексам для таких простых сценариев.

Пример атомарного счетчика:

import "sync/atomic"

var counter int64

// Безопасно увеличивает счетчик из любой горутины
func increment() {
    atomic.AddInt64(&counter, 1)
}

// Безопасно читает значение счетчика
func get() int64 {
    return atomic.LoadInt64(&counter)
}

Ключевые операции:

  • Add: Атомарное сложение (или вычитание при передаче отрицательного числа).
  • Load: Атомарное чтение значения.
  • Store: Атомарная запись значения.
  • Swap: Атомарная замена значения на новое с возвратом старого.
  • CompareAndSwap (CAS): Атомарно сравнивает значение с ожидаемым и, если они совпадают, заменяет его новым. Возвращает true в случае успеха. Это основа для реализации многих lock-free алгоритмов.

Ограничения:

  • Работает только с числовыми примитивами (int32, int64, uint32, uint64, uintptr) и unsafe.Pointer.
  • Не подходит для синхронизации сложных операций, затрагивающих несколько переменных. В таких случаях следует использовать мьютексы (sync.Mutex).