Атомарные операции (atomics) — это низкоуровневые примитивы синхронизации, обеспечивающие безопасное изменение общих данных между горутинами без использования мьютексов.
Go предоставляет атомарные операции через пакет sync/atomic
:
int32
, int64
- атомарные целые числаuint32
, uint64
- беззнаковые атомарные целыеuintptr
- атомарные указателиunsafe.Pointer
- атомарные операции с указателямиValue
- универсальный контейнер для любых значений (с Go 1.4)var counter int64
// Атомарная запись
atomic.StoreInt64(&counter, 10)
// Атомарное чтение
val := atomic.LoadInt64(&counter)
atomic.AddInt64(&counter, 1) // Инкремент
atomic.AddInt64(&counter, -1) // Декремент
old := atomic.LoadInt64(&counter)
new := old + 1
if !atomic.CompareAndSwapInt64(&counter, old, new) {
// Кто-то успел изменить значение
}
old := atomic.SwapInt64(&counter, 100) // Устанавливает новое и возвращает старое
Универсальный контейнер для атомарного хранения любых значений:
var config atomic.Value
// Сохранение
config.Store(Config{Timeout: 10})
// Загрузка
cfg := config.Load().(Config)
Счетчики и простые флаги
Идеально для высоконагруженных счетчиков
Read-mostly данные
Когда обновления редки, а чтения частые
Низкоуровневая синхронизация
При реализации собственных примитивов синхронизации
Performance-critical участки
Где мьютексы слишком медленные
Высокая производительность
Нет блокировок, только CPU-атомарные инструкции
Отсутствие deadlock
Нет проблем с вложенными блокировками
Меньшие накладные расходы
Особенно заметно в высококонкурентных сценариях
Только простые типы
Не подходит для сложных структур данных
Нет защиты от гонок за данными
Только для отдельных переменных
Сложность отладки
Тонкие ошибки сложнее обнаружить
ABA проблема
В сложных CAS-сценариях
type AtomicCounter struct {
value int64
}
func (c *AtomicCounter) Increment() {
atomic.AddInt64(&c.value, 1)
}
func (c *AtomicCounter) Value() int64 {
return atomic.LoadInt64(&c.value)
}
var isRunning uint32
func Start() {
if atomic.CompareAndSwapUint32(&isRunning, 0, 1) {
go worker()
}
}
type Stack struct {
head unsafe.Pointer
}
func (s *Stack) Push(value interface{}) {
newHead := &Element{value: value}
for {
oldHead := atomic.LoadPointer(&s.head)
newHead.next = oldHead
if atomic.CompareAndSwapPointer(&s.head, oldHead, unsafe.Pointer(newHead)) {
return
}
}
}
Метод | Простота | Производительность | Безопасность | Применимость |
---|---|---|---|---|
Atomics | Средняя | Очень высокая | Средняя | Простые счетчики/флаги |
Мьютексы | Высокая | Средняя | Высокая | Любые данные |
Каналы | Высокая | Низкая | Высокая | Коммуникация |
Что такое atomics
Низкоуровневые операции для безопасного изменения переменных между горутинами
Основные типы
int32/64, uint32/64, uintptr, unsafe.Pointer, Value
Когда использовать
Когда не использовать
Атомарные операции — мощный инструмент для опытных разработчиков, но требуют аккуратного применения.