Ответ
Атомарные (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
}
Когда это впердоливать в код?
Идеально для счётчиков, флажков "включено/выключено" или любых других простых разделяемых переменных, где скорость — это всё, а мьютекс — это овердохуища накладных расходов. Но если тебе нужно охранять целую структуру данных с кучей полей — тут уже, дружок, атомарными операциями не отделаешься, нужен полноценный мьютекс, иначе будет пиздец и рассинхрон.