← Назад к вопросам

Что можно делать с помощью atomic?

1.3 Junior🔥 171 комментариев
#Конкурентность и горутины

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Работа с атомарными операциями в Go с помощью atomic

Пакет sync/atomic в Go предоставляет низкоуровневые атомарные операции с памятью для реализации потокобезопасного доступа к простым типам данных без использования мьютексов. Эти операции гарантируют, что чтение или запись значения будет выполнено как единая неделимая операция, что исключает состояние гонки (race condition) при работе с несколькими горутинами.

Основные возможности пакета atomic

1. Атомарные операции с целочисленными типами

Пакет поддерживает int32, int64, uint32, uint64, uintptr. Доступны операции:

  • Чтение и запись: LoadInt32, StoreInt64
  • Арифметические операции: AddInt32, AddUint64
  • Логические операции: AndInt32, OrUint64
  • Сравнение с обменом (CAS): CompareAndSwapInt32
  • Подкачка значений: SwapInt64
package main

import (
    "fmt"
    "sync/atomic"
    "time"
)

func main() {
    var counter int32
    
    // Атомарное увеличение счетчика из 10 горутин
    for i := 0; i < 10; i++ {
        go func() {
            for j := 0; j < 1000; j++ {
                atomic.AddInt32(&counter, 1)
            }
        }()
    }
    
    time.Sleep(2 * time.Second)
    fmt.Println("Итоговое значение:", atomic.LoadInt32(&counter)) // Всегда 10000
}

2. Атомарные операции с указателями

Пакет позволяет атомарно работать с указателями любого типа через unsafe.Pointer:

  • LoadPointer, StorePointer
  • CompareAndSwapPointer, SwapPointer
type Config struct {
    Value string
}

func updateConfig(atomicConfig *atomic.Value) {
    newConfig := &Config{Value: "новые настройки"}
    atomicConfig.Store(newConfig)
}

func readConfig(atomicConfig *atomic.Value) *Config {
    return atomicConfig.Load().(*Config)
}

3. Атомарные операции с atomic.Value

atomic.Value предоставляет типобезопасный контейнер для атомарного хранения и извлечения значений любого типа:

  • Store(value interface{}) - атомарно сохраняет значение
  • Load() interface{} - атомарно загружает значение
var sharedValue atomic.Value

// Горутина 1
sharedValue.Store("первое значение")

// Горутина 2
if val := sharedValue.Load(); val != nil {
    fmt.Println("Получено:", val.(string))
}

Типичные сценарии использования atomic

1. Счетчики и метрики

Когда нужно обеспечить точный подсчет событий в конкурентной среде:

type Metrics struct {
    requestCount atomic.Int64
    errorCount   atomic.Uint32
}

func (m *Metrics) IncrementRequests() {
    m.requestCount.Add(1)
}

func (m *Metrics) GetRequestCount() int64 {
    return m.requestCount.Load()
}

2. Флаги и состояния

Для управления простыми состояниями без блокировок:

var isRunning atomic.Bool

func startService() {
    if isRunning.CompareAndSwap(false, true) {
        // Запускаем сервис только если он еще не запущен
        go runService()
    }
}

3. Обновление конфигурации без блокировок

Техника "copy-on-write" для читаемых конфигураций:

type AppConfig struct {
    Timeout int
    Hosts   []string
}

var currentConfig atomic.Value

func UpdateConfig(newConfig AppConfig) {
    currentConfig.Store(newConfig) // Атомарная замена всей конфигурации
}

// Множество горутин может одновременно читать конфигурацию
func GetConfig() AppConfig {
    return currentConfig.Load().(AppConfig)
}

4. Реализация мьютексов и примитивов синхронизации

Низкоуровневая реализация примитивов синхронизации:

type SpinLock struct {
    state atomic.Int32
}

func (sl *SpinLock) Lock() {
    for !sl.state.CompareAndSwap(0, 1) {
        runtime.Gosched() // Уступаем процессор
    }
}

func (sl *SpinLock) Unlock() {
    sl.state.Store(0)
}

Важные ограничения и рекомендации

  1. Производительность vs читаемость: Атомарные операции обычно быстрее мьютексов для простых операций, но усложняют код

  2. Порядок операций: Гарантируется только атомарность, но не порядок выполнения операций между разными горутинами

  3. Только для простых типов: Не подходит для сложных структурных изменений, где нужна согласованность нескольких полей

  4. Memory ordering: В Go модель памяти гарантирует, что атомарные операции имеют семантику последовательной согласованности

// ПРАВИЛЬНО: атомарные операции для простого счетчика
var count atomic.Int64
count.Add(1)

// НЕПРАВИЛЬНО: попытка использовать atomic для сложной логики
type User struct {
    Name string
    Age  int
}
var user atomic.Value // Так можно, но изменение отдельных полей структуры не будет атомарным

Сравнение с другими подходами

Критерийsync/atomicsync.MutexКаналы
СложностьВысокаяСредняяНизкая
ПроизводительностьМаксимальнаяСредняяНиже средней
ЧитаемостьНизкаяСредняяВысокая
ПрименимостьПростые атомарные операцииСложные критические секцииКоммуникация между горутинами

Заключение

Пакет sync/atomic в Go — это мощный инструмент для опытных разработчиков, который позволяет создавать высокопроизводительные конкурентные структуры данных. Он идеально подходит для:

  • Высокочастотных счетчиков и метрик
  • Флагов состояния
  • Обновления конфигураций "на лету"
  • Реализации низкоуровневых примитивов синхронизации

Однако для большинства повседневных задач рекомендуется начинать с мьютексов или каналов, переходя к atomic только при доказанных проблемах с производительностью и полном понимании последствий. Неправильное использование атомарных операций может привести к тонким и сложно отлавливаемым ошибкам в многопоточном коде.

Что можно делать с помощью atomic? | PrepBro