Что такое атомарные операции в Go (пакет `sync/atomic`) и когда их стоит применять?

Ответ

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

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

Пример: безопасный счетчик

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

func main() {
    var counter int64
    var wg sync.WaitGroup

    // Запускаем 1000 горутин, каждая инкрементирует счетчик 100 раз
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for j := 0; j < 100; j++ {
                // Безопасно увеличиваем счетчик на 1
                atomic.AddInt64(&counter, 1)
            }
        }()
    }

    wg.Wait()
    // Читаем итоговое значение атомарно
    finalValue := atomic.LoadInt64(&counter)
    fmt.Println("Final Counter:", finalValue) // Всегда будет 100000
}

Когда использовать атомарные операции (вместо мьютексов):

  1. Для простых операций со счетчиками или флагами: Когда нужно просто увеличить число, установить флаг (true/false) или обновить один указатель. Для таких задач мьютекс является избыточным.
  2. Для оптимизации производительности в критических участках кода: Когда блокировка мьютекса создает заметное узкое место, атомарные операции могут дать существенный прирост скорости.
  3. При реализации lock-free алгоритмов: Для создания сложных конкурентных структур данных, которые работают без использования блокировок.

Основные функции:

  • AddInt64, AddUint64, и т.д. — атомарное сложение.
  • LoadInt64, LoadPointer — атомарное чтение значения.
  • StoreInt64, StorePointer — атомарная запись значения.
  • SwapInt64 — атомарно записывает новое значение и возвращает старое.
  • CompareAndSwapInt64 (CAS) — атомарно сравнивает значение с ожидаемым и, если они равны, записывает новое. Это основа для многих lock-free алгоритмов.