Что такое atomic-операции в Go и для чего нужен пакет `sync/atomic`?

Ответ

Атомарные (atomic) операции в Go — это операции, которые выполняются как единое, неделимое действие, без риска быть прерванными другим потоком (горутиной). Это гарантирует потокобезопасность при работе с разделяемыми данными без необходимости использования более медленных примитивов синхронизации, таких как мьютексы.

Пакет sync/atomic предоставляет низкоуровневые атомарные примитивы, которые напрямую транслируются в инструкции процессора.

Ключевые преимущества:

  • Производительность: Атомарные операции значительно быстрее мьютексов для простых сценариев (например, инкремент счетчика), так как они не вызывают блокировку потока и переключение контекста планировщиком.
  • Отсутствие блокировок (Lock-Free): Позволяют создавать неблокирующие алгоритмы.

Основные операции в sync/atomic:

  • Add: Атомарно добавляет значение (например, AddInt64).
  • Load: Атомарно читает значение.
  • Store: Атомарно записывает значение.
  • Swap: Атомарно записывает новое значение и возвращает старое.
  • CompareAndSwap (CAS): Атомарно сравнивает значение с ожидаемым и, если они совпадают, заменяет его новым. Это одна из самых мощных атомарных операций.

Пример использования:

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

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

    wg.Add(100)
    for i := 0; i < 100; i++ {
        go func() {
            defer wg.Done()
            // Безопасный инкремент счетчика из разных горутин
            atomic.AddInt64(&counter, 1)
        }()
    }

    wg.Wait()

    // Для чтения также используем атомарную операцию
    finalValue := atomic.LoadInt64(&counter)
    fmt.Println("Final Counter:", finalValue) // => Final Counter: 100
}

Когда использовать:

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

Ответ 18+ 🔞

Смотри, есть такая штука в Go — атомарные операции. Это как если бы ты в магазине взял последнюю пачку пельменей, и пока ты её несёшь к кассе, никакой другой мудак не может выхватить её у тебя из рук. Операция завершается целиком, или не начинается вообще. Никаких "ой, я только посмотреть". В контексте горутин — одна горутина делает своё дело с переменной, и другие в этот момент не лезут, всё честно.

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

В чём соль, блядь:

  • Скорость, ёпта: Для простых действий вроде "прибавь единичку" атомарные операции — просто ракета. Мьютекс там будет городить огород с блокировками, переключениями, а тут — чих-пых и готово.
  • Без блокировок: Можно строить хитрожопые алгоритмы, где все бегают, делают что надо, и не ждут друг друга как идиоты.

Что там есть в этом sync/atomic:

  • Add — атомарно прибавить. Типа AddInt64.
  • Load — атомарно прочитать. Чтобы не вышло, что читаешь ты одно, а в это время оно уже поменялось.
  • Store — атомарно записать. Раз — и всё, значение на месте.
  • Swap — атомарно подменить значение и вернуть то, что было. Как карточный фокус.
  • CompareAndSwap (CAS) — вот это вообще магия, блядь. "Если переменная равна тому, что я ожидаю, то меняю её на новое значение, иначе — иди нахуй". Основа многих нетривиальных штук.

Смотри, как это выглядит в деле:

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

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

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

    wg.Wait()

    // Читаем тоже атомарно, чтобы не поймать какую-нибудь дичь
    finalValue := atomic.LoadInt64(&counter)
    fmt.Println("Final Counter:", finalValue) // => Final Counter: 100
}

Когда это впердоливать в код?

Идеально для счётчиков, флажков "включено/выключено" или любых других простых разделяемых переменных, где скорость — это всё, а мьютекс — это овердохуища накладных расходов. Но если тебе нужно охранять целую структуру данных с кучей полей — тут уже, дружок, атомарными операциями не отделаешься, нужен полноценный мьютекс, иначе будет пиздец и рассинхрон.