Описание пакета sync/atomic
Для правильного использования этих функций требуется большая осторожность. За исключением специальных низкоуровневых приложений, синхронизацию лучше выполнять с помощью каналов или средств пакета sync. Делитесь памятью, общаясь; не общайтесь, делясь памятью.
Объяснение atomic
Низкоуровневые примитивы атомарной памяти в Go
Атомарные операции - это операции, которые выполняются полностью (как единое целое) без возможности прерывания их другими потоками. В Go они предоставляются пакетом
sync/atomic
и используются для безопасной работы с разделяемой памятью в многопоточных программах.
Основные понятия
- Атомарность - операция либо выполняется полностью, либо не выполняется вообще
- Гонка данных (data race) - ситуация, когда несколько горутин одновременно обращаются к одной переменной, и хотя бы одна из них выполняет запись
- Примитивы - базовые строительные блоки для синхронизации
Основные типы и функции
Пакет sync/atomic
работает с:
int32
,int64
,uint32
,uint64
,uintptr
- Указателями (через
unsafe.Pointer
)
Примеры использования
1. Атомарное увеличение счетчика
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var counter int64
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
atomic.AddInt64(&counter, 1)
wg.Done()
}()
}
wg.Wait()
fmt.Println("Counter:", counter) // Гарантированно выведет 100
}
2. Атомарное чтение и запись
package main
import (
"fmt"
"sync/atomic"
"time"
)
func main() {
var value int32
// Горутина, которая изменяет значение
go func() {
time.Sleep(time.Millisecond * 100)
atomic.StoreInt32(&value, 42)
}()
// Главная горутина ждет изменения значения
for {
if atomic.LoadInt32(&value) == 42 {
fmt.Println("Value has been updated to 42")
break
}
time.Sleep(time.Millisecond * 10)
}
}
3. Сравнение и замена (CAS - Compare And Swap)
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var value int32 = 10
// Пытаемся заменить 10 на 20
swapped := atomic.CompareAndSwapInt32(&value, 10, 20)
fmt.Println("Swapped:", swapped, "Value:", value) // Swapped: true Value: 20
// Пытаемся заменить 10 на 30 (но текущее значение уже 20)
swapped = atomic.CompareAndSwapInt32(&value, 10, 30)
fmt.Println("Swapped:", swapped, "Value:", value) // Swapped: false Value: 20
}
4. Атомарный указатель
package main
import (
"fmt"
"sync/atomic"
"unsafe"
)
type Data struct {
value int
}
func main() {
var ptr unsafe.Pointer
// Создаем данные
data1 := &Data{value: 42}
data2 := &Data{value: 100}
// Атомарно сохраняем указатель
atomic.StorePointer(&ptr, unsafe.Pointer(data1))
// Атомарно читаем указатель
current := (*Data)(atomic.LoadPointer(&ptr))
fmt.Println("Current value:", current.value) // 42
// Атомарно меняем указатель
atomic.StorePointer(&ptr, unsafe.Pointer(data2))
current = (*Data)(atomic.LoadPointer(&ptr))
fmt.Println("New value:", current.value) // 100
}
Когда использовать atomic
- Когда нужны простые операции над разделяемыми переменными
- Когда производительность критична (atomic обычно быстрее мьютексов)
- Для реализации более сложных примитивов синхронизации
Когда НЕ использовать atomic
- Когда логика сложная - лучше использовать мьютексы или каналы
- Когда нужно работать со сложными структурами данных
- Когда важна читаемость кода (atomic код может быть сложнее для понимания)
Атомарные операции - это низкоуровневый механизм, который требует аккуратного использования, но может быть очень эффективным в правильных сценариях.
Операция обмена, реализуемая функциями SwapT, является атомарным эквивалентом:
old = *addr
*addr = new
return old
Операция сравнения и замены, реализуемая функциями CompareAndSwapT, является атомарным эквивалентом:
if *addr == old {
*addr = new
return true
}
return false
Операция сложения, реализуемая функциями AddT, является атомарным эквивалентом:
*addr += delta
return *addr
Операции load и store, реализуемые функциями LoadT и StoreT, являются атомарными эквивалентами “return *addr” и “*addr = val”.
В терминологии модели памяти Go, если эффект атомарной операции A наблюдается атомарной операцией B, то A “синхронизируется раньше” B. Кроме того, все атомарные операции, выполняемые в программе, ведут себя так, как будто выполняются в некотором последовательно согласованном порядке. Это определение обеспечивает ту же семантику, что и последовательные атомики в C++ и волатильные переменные в Java.
Функции
func AddInt32
func AddInt32(addr *int32, delta int32) (new int32)
AddInt32 атомарно добавляет delta к *addr и возвращает новое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.Add.
func AddInt64
func AddInt64(addr *int64, delta int64) (new int64)
AddInt64 атомарно добавляет delta к *addr и возвращает новое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.Add (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).
func AddUint32
func AddUint32(addr *uint32, delta uint32) (new uint32)
AddUint32 атомарно добавляет дельту к *addr и возвращает новое значение. Чтобы вычесть из x знаковое положительное константное значение c, выполните AddUint32(&x, ^uint32(c-1)). В частности, чтобы уменьшить x, выполните AddUint32(&x, ^uint32(0)). Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.Add.
func AddUint64
func AddUint64(addr *uint64, delta uint64) (new uint64)
AddUint64 атомарно добавляет delta к *addr и возвращает новое значение. Чтобы вычесть из x знаковое положительное константное значение c, выполните AddUint64(&x, ^uint64(c-1)). В частности, чтобы уменьшить x, выполните AddUint64(&x, ^uint64(0)). Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.Add (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).
func AddUintptr
func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)
AddUintptr атомарно добавляет delta к *addr и возвращает новое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.Add.
func AndInt32
func AndInt32(addr *int32, mask int32) (old int32)
AndInt32 атомарно выполняет побитовую операцию AND над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.And.
func AndInt64
func AndInt64(addr *int64, mask int64) (old int64)
AndInt64 атомарно выполняет побитовую операцию AND над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.And вместо этого.
func AndUint32
func AndUint32(addr *uint32, mask uint32) (old uint32)
AndUint32 атомарно выполняет побитовую операцию AND над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.And вместо него.
func AndUint64
func AndUint64(addr *uint64, mask uint64) (old uint64)
AndUint64 атомарно выполняет побитовую операцию AND над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.And вместо этого.
func AndUintptr
func AndUintptr(addr *uintptr, mask uintptr) (old uintptr)
AndUintptr атомарно выполняет побитовую операцию AND над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.And вместо этого.
func CompareAndSwapInt32
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
CompareAndSwapInt32 выполняет операцию сравнения и замены для значения int32. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.CompareAndSwap.
func CompareAndSwapInt64
func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)
CompareAndSwapInt64 выполняет операцию сравнения и замены для значения int64. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.CompareAndSwap (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).
func CompareAndSwapPointer
func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)
CompareAndSwapPointer выполняет операцию сравнения и обмена для значения unsafe.Pointer. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Pointer.CompareAndSwap.
func CompareAndSwapUint32
func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool)
CompareAndSwapUint32 выполняет операцию сравнения и замены для значения uint32. Рассмотрите возможность использования более удобной и менее подверженной ошибкам функции Uint32.CompareAndSwap.
func CompareAndSwapUint64
func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool)
CompareAndSwapUint64 выполняет операцию сравнения и замены для значения uint64. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.CompareAndSwap (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).
func CompareAndSwapUintptr
func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool)
CompareAndSwapUintptr выполняет операцию сравнения и замены для значения uintptr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.CompareAndSwap.
func LoadInt32
func LoadInt32(addr *int32) (val int32)
LoadInt32 атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.Load.
func LoadInt64
func LoadInt64(addr *int64) (val int64)
LoadInt64 атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.Load (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).
func LoadPointer
func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
LoadPointer атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Pointer.Load.
func LoadUint32
func LoadUint32(addr *uint32) (val uint32)
LoadUint32 атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.Load.
func LoadUint64
func LoadUint64(addr *uint64) (val uint64)
LoadUint64 атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.Load (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).
func LoadUintptr
func LoadUintptr(addr *uintptr) (val uintptr)
LoadUintptr атомарно загружает *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.Load.
func OrInt32
func OrInt32(addr *int32, mask int32) (old int32)
OrInt32 атомарно выполняет побитовую операцию OR над *addr, используя битовую маску, указанную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.Or.
func OrInt64
func OrInt64(addr *int64, mask int64) (old int64)
OrInt64 атомарно выполняет побитовую операцию OR над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.Or вместо него.
func OrUint32
func OrUint32(addr *uint32, mask uint32) (старый uint32)
OrUint32 атомарно выполняет побитовую операцию OR над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.Or.
func OrUint64
func OrUint64(addr *uint64, mask uint64) (old uint64)
OrUint64 атомарно выполняет побитовую операцию OR над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.Or вместо него.
func OrUintptr
func OrUintptr(addr *uintptr, mask uintptr) (old uintptr)
OrUintptr атомарно выполняет побитовую операцию OR над *addr, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.Or.
func StoreInt32
func StoreInt32(addr *int32, val int32)
StoreInt32 атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.Store.
func StoreInt64
func StoreInt64(addr *int64, val int64)
StoreInt64 атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.Store (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).
func StorePointer
func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)
StorePointer атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Pointer.Store.
func StoreUint32
func StoreUint32(addr *uint32, val uint32)
StoreUint32 атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.Store.
func StoreUint64
func StoreUint64(addr *uint64, val uint64)
StoreUint64 атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.Store (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).
func StoreUintptr
func StoreUintptr(addr *uintptr, val uintptr)
StoreUintptr атомарно сохраняет val в *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.Store.
func SwapInt32
func SwapInt32(addr *int32, new int32) (old int32)
SwapInt32 атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int32.Swap.
func SwapInt64
func SwapInt64(addr *int64, new int64) (old int64)
SwapInt64 атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Int64.Swap (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).
func SwapPointer
func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)
SwapPointer атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Pointer.Swap.
func SwapUint32
func SwapUint32(addr *uint32, new uint32) (old uint32)
SwapUint32 атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint32.Swap.
func SwapUint64
func SwapUint64(addr *uint64, new uint64) (old uint64)
SwapUint64 атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uint64.Swap (особенно если вы ориентируетесь на 32-разрядные платформы; см. раздел «Ошибки»).
func SwapUintptr
func SwapUintptr(addr *uintptr, new uintptr) (old uintptr)
SwapUintptr атомарно сохраняет new в *addr и возвращает предыдущее значение *addr. Рассмотрите возможность использования более эргономичного и менее подверженного ошибкам Uintptr.Swap.
Типы
type Bool
type Bool struct {
// содержит отфильтрованные или неэкспортируемые поля
}
Bool — это атомарное булево значение. Нулевое значение — false.
func (*Bool) CompareAndSwap
func (x *Bool) CompareAndSwap(old, new bool) (swapped bool)
CompareAndSwap выполняет операцию сравнения и замены для булевого значения x.
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var flag atomic.Bool
flag.Store(false)
// Пытаемся изменить false → true (успешно)
swapped := flag.CompareAndSwap(false, true)
fmt.Println("Swapped (false→true):", swapped, "Flag:", flag.Load())
// Пытаемся изменить false → true (неуспешно, текущее значение уже true)
swapped = flag.CompareAndSwap(false, true)
fmt.Println("Swapped (false→true):", swapped, "Flag:", flag.Load())
}
func (*Bool) Load
func (x *Bool) Load() bool
Load атомарно загружает и возвращает значение, хранящееся в x.
package main
import (
"fmt"
"sync"
"sync/atomic"
"time"
)
func main() {
var ready atomic.Bool
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
time.Sleep(500 * time.Millisecond)
ready.Store(true)
}()
for !ready.Load() {
fmt.Println("Waiting...")
time.Sleep(100 * time.Millisecond)
}
fmt.Println("Ready!")
wg.Wait()
}
func (*Bool) Store
func (x *Bool) Store(val bool)
Store атомарно сохраняет val в x.
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var initialized atomic.Bool
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
if initialized.Load() {
fmt.Printf("Worker %d: already initialized\n", id)
return
}
initialized.Store(true)
fmt.Printf("Worker %d: did initialization\n", id)
}(i)
}
wg.Wait()
}
func (*Bool) Swap
func (x *Bool) Swap(new bool) (old bool)
Swap атомарно сохраняет new в x и возвращает предыдущее значение.
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var toggle atomic.Bool
toggle.Store(false)
// Переключаем значение и получаем старое
old := toggle.Swap(true)
fmt.Println("Old value:", old, "New value:", toggle.Load())
// Переключаем ещё раз
old = toggle.Swap(false)
fmt.Println("Old value:", old, "New value:", toggle.Load())
}
type Int32/Int64/Uint32/Uint64/Uintptr
type Int32 struct {
// содержит отфильтрованные или неэкспортируемые поля
}
Int32 — это атомарное целое число int32. Нулевое значение — ноль. Int64 — это атомарное целое число int64. Нулевое значение — ноль. Uint32 - это атомарный uint32. Нулевое значение равно нулю. Uint64 - это атомарный uint64. Нулевое значение равно нулю. Uintptr - это атомарный uintptr. Нулевое значение равно нулю.
Пример
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var num atomic.Int32
// Store - установка начального значения
num.Store(10)
fmt.Printf("Store(10): %d\n", num.Load())
// Add - атомарное добавление
newVal := num.Add(5)
fmt.Printf("Add(5): new=%d, loaded=%d\n", newVal, num.Load())
// Swap - замена значения
old := num.Swap(20)
fmt.Printf("Swap(20): old=%d, new=%d\n", old, num.Load())
// CompareAndSwap - условная замена
swapped := num.CompareAndSwap(20, 30)
fmt.Printf("CompareAndSwap(20→30): %t, value=%d\n", swapped, num.Load())
// Пытаемся сделать замену с неверным old значением
swapped = num.CompareAndSwap(20, 40)
fmt.Printf("CompareAndSwap(20→40): %t, value=%d\n", swapped, num.Load())
// Побитовые операции
// Устанавливаем значение для битовых операций
num.Store(0b00001111) // 15 в десятичной
// And - побитовое И
old = num.And(0b00110011) // 0b00000011 = 3
fmt.Printf("And(0b00110011): old=0b%08b, new=0b%08b (%d)\n",
old, num.Load(), num.Load())
// Or - побитовое ИЛИ
old = num.Or(0b11001100) // 0b11001111 = 207
fmt.Printf("Or(0b11001100): old=0b%08b, new=0b%08b (%d)\n",
old, num.Load(), num.Load())
// Load - просто чтение
current := num.Load()
fmt.Printf("Load(): %d (0b%08b)\n", current, current)
}
Вывод программы:
Store(10): 10
Add(5): new=15, loaded=15
Swap(20): old=15, new=20
CompareAndSwap(20→30): true, value=30
CompareAndSwap(20→40): false, value=30
And(0b00110011): old=0b00001111, new=0b00000011 (3)
Or(0b11001100): old=0b00000011, new=0b11001111 (207)
Load(): 207 (0b11001111)
Объяснение Uintptr
atomic.Uintptr
в Go
atomic.Uintptr
— это тип, добавленный в Go 1.19, который предоставляет атомарные операции для беззнаковых целых чисел размером с указатель (uintptr
).
Основное назначение
-
Атомарные операции с указателями:
uintptr
часто используется для низкоуровневых операций с указателямиatomic.Uintptr
позволяет безопасно работать с ними в конкурентных сценариях
-
Реализация lock-free структур данных:
- Полезен при создании собственных конкурентных структур данных
- Позволяет избежать блокировок при определенных операциях
-
Работа с небезопасными указателями:
- Когда нужно сохранить
uintptr
значение атомарно (например, при работе черезunsafe
)
- Когда нужно сохранить
Ключевые особенности
- Обеспечивает атомарность операций без использования мьютексов
- Поддерживает все стандартные атомарные операции:
func (u *Uintptr) Add(delta uintptr) (new uintptr) func (u *Uintptr) Load() uintptr func (u *Uintptr) Store(val uintptr) func (u *Uintptr) Swap(new uintptr) (old uintptr) func (u *Uintptr) CompareAndSwap(old, new uintptr) (swapped bool)
Пример использования
package main
import (
"fmt"
"sync/atomic"
"unsafe"
)
type Data struct {
value int
}
func main() {
var addr atomic.Uintptr
data := &Data{value: 42}
// Сохраняем указатель как uintptr
addr.Store(uintptr(unsafe.Pointer(data)))
// Атомарно загружаем и преобразуем обратно
ptr := (*Data)(unsafe.Pointer(addr.Load()))
fmt.Println("Data value:", ptr.value) // 42
// Атомарное добавление к значению (если используется как число)
var counter atomic.Uintptr
counter.Store(100)
newVal := counter.Add(50)
fmt.Println("Counter:", newVal) // 150
}
Когда использовать
- При работе с
unsafe.Pointer
и низкоуровневыми операциями - Для атомарного управления памятью или ресурсами
- В высокопроизводительных сценариях, где важна lock-free синхронизация
Отличие от других атомарных типов
Uintptr
работает именно с размером указателя (архитектурно-зависимым)- Более безопасная альтернатива прямому использованию
atomic.AddUintptr
и подобных функций - Типобезопасная обертка (по сравнению с использованием
atomic
функций напрямую)
Этот тип особенно полезен в системном программировании и при реализации высокопроизводительных параллельных алгоритмов.
func (*Int32) Add
func (x *Int32) Add(delta int32) (new int32)
Add атомарно добавляет delta к x и возвращает новое значение.
func (*Int32) And
func (x *Int32) And(mask int32) (old int32)
And атомарно выполняет побитовую операцию AND над x, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение.
func (*Int32) CompareAndSwap
func (x *Int32) CompareAndSwap(old, new int32) (swapped bool)
CompareAndSwap выполняет операцию сравнения и замены для x.
func (*Int32) Load
func (x *Int32) Load() int32
Load атомарно загружает и возвращает значение, хранящееся в x.
func (*Int32) Or
func (x *Int32) Or(mask int32) (old int32)
Or атомарно выполняет операцию побитового ИЛИ над x, используя битовую маску, предоставленную в качестве mask, и возвращает старое значение.
func (*Int32) Store
func (x *Int32) Store(val int32)
Store атомарно сохраняет val в x.
func (*Int32) Swap
func (x *Int32) Swap(new int32) (old int32)
Swap атомарно сохраняет new в x и возвращает предыдущее значение.
type Pointer
type Pointer[T any] struct {
// содержит отфильтрованные или неэкспортируемые поля
}
Pointer — это атомарный указатель типа *T. Нулевое значение — nil *T.
Пример
package main
import (
"fmt"
"sync/atomic"
)
type Data struct {
value int
}
func main() {
var ptr atomic.Pointer[Data]
// Исходные данные
data1 := &Data{value: 10}
data2 := &Data{value: 20}
data3 := &Data{value: 30}
// Store - атомарное сохранение указателя
ptr.Store(data1)
printState("Store(data1)", &ptr)
// Load - атомарное чтение указателя
current := ptr.Load()
fmt.Printf("Load(): %+v\n", current)
// Swap - атомарная замена указателя
old := ptr.Swap(data2)
printState("Swap(data2)", &ptr)
fmt.Printf("Old value: %+v\n", old)
// CompareAndSwap успешный (текущее значение = data2)
swapped := ptr.CompareAndSwap(data2, data3)
printState("CompareAndSwap(data2→data3)", &ptr)
fmt.Printf("Swapped: %v\n", swapped)
// CompareAndSwap неудачный (текущее значение уже data3)
swapped = ptr.CompareAndSwap(data2, data1)
printState("CompareAndSwap(data2→data1) [expected fail]", &ptr)
fmt.Printf("Swapped: %v\n", swapped)
// Демонстрация конкурентного доступа
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
for i := 0; i < 1000; i++ {
ptr.Store(&Data{value: i})
}
}()
go func() {
defer wg.Done()
for i := 0; i < 1000; i++ {
_ = ptr.Load()
}
}()
wg.Wait()
fmt.Println("Concurrent access completed")
}
func printState(op string, p *atomic.Pointer[Data]) {
fmt.Printf("%-30s: %+v\n", op, p.Load())
}
Вывод программы:
Store(data1) : &{value:10}
Load(): &{value:10}
Swap(data2) : &{value:20}
Old value: &{value:10}
CompareAndSwap(data2→data3) : &{value:30}
Swapped: true
CompareAndSwap(data2→data1) [expected fail]: &{value:30}
Swapped: false
Concurrent access completed
func (*Pointer[T]) CompareAndSwap
func (x *Pointer[T]) CompareAndSwap(old, new *T) (swapped bool)
CompareAndSwap выполняет операцию сравнения и замены для x.
func (*Pointer[T]) Load
func (x *Pointer[T]) Load() *T
Load атомарно загружает и возвращает значение, хранящееся в x.
func (*Pointer[T]) Store
func (x *Pointer[T]) Store(val *T)
Store атомарно сохраняет val в x.
func (*Pointer[T]) Swap
func (x *Pointer[T]) Swap(new *T) (old *T)
Swap атомарно сохраняет new в x и возвращает предыдущее значение.
type Value
type Value struct {
// содержит отфильтрованные или неэкспортируемые поля
}
Value обеспечивает атомарную загрузку и хранение значения с постоянным типом. Нулевое значение для Value возвращает nil из Value.Load. После вызова Value.Store Value не должен копироваться.
Value не должен копироваться после первого использования.
Объяснение Value
atomic.Value
— это контейнер для безопасного хранения и обмена значениями между горутинами. Главная особенность — все операции с ним атомарны (не требуют мьютексов).
Основные правила:
- После первого использования нельзя копировать Value
- Все хранимые значения должны быть одного типа
- Нельзя хранить nil
Примеры использования:
1. Базовое использование (Store/Load)
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var config atomic.Value
// Сохраняем конфиг
config.Store(map[string]string{
"host": "localhost",
"port": "8080",
})
// Читаем конфиг в другой горутине
go func() {
c := config.Load().(map[string]string)
fmt.Println("Config in goroutine:", c["host"])
}()
// Читаем в main
fmt.Println("Port:", config.Load().(map[string]string)["port"])
}
2. Swap (атомарная замена)
var lastUser atomic.Value
func updateUser(user string) {
old := lastUser.Swap(user)
fmt.Printf("User changed from %v to %v\n", old, user)
}
func main() {
updateUser("Alice") // User changed from <nil> to Alice
updateUser("Bob") // User changed from Alice to Bob
}
3. CompareAndSwap (условная замена)
var currentID atomic.Value
func updateID(expected, new int) bool {
return currentID.CompareAndSwap(expected, new)
}
func main() {
currentID.Store(100)
success := updateID(100, 200)
fmt.Println("Update 100→200:", success) // true
success = updateID(100, 300)
fmt.Println("Update 100→300:", success) // false (текущее значение уже 200)
}
4. Хранение структур
type Config struct {
Timeout int
Mode string
}
func main() {
var cfg atomic.Value
cfg.Store(Config{Timeout: 30, Mode: "production"})
// Обновление конфига
newCfg := cfg.Load().(Config)
newCfg.Timeout = 60
cfg.Store(newCfg)
}
Когда использовать:
- Для “горячей” замены конфигурации без блокировок
- При частом чтении и редком обновлении данных
- Для реализации кэшей с атомарным обновлением
На заметку
- При Load всегда нужно делать type assertion (
.(type)
) - Тип значения нельзя менять после первого Store
- Для сложных структур лучше хранить указатели (но не nil)
Пример config
package main
import (
"fmt"
"math/rand"
"sync/atomic"
"time"
)
// ServerConfig содержит конфигурацию сервера
type ServerConfig struct {
Host string
Port int
Timeout time.Duration
Debug bool
}
var (
config atomic.Value // Текущая конфигурация
counter atomic.Int32 // Счетчик запросов
)
func loadConfig() ServerConfig {
// Имитация загрузки конфигурации (в реальности может быть из файла, БД и т.д.)
rand.Seed(time.Now().UnixNano())
return ServerConfig{
Host: fmt.Sprintf("server-%d.example.com", rand.Intn(3)+1),
Port: 8000 + rand.Intn(100),
Timeout: time.Duration(rand.Intn(5)+1) * time.Second,
Debug: rand.Intn(2) == 0,
}
}
func processRequest(workerID int) {
for {
// Имитация обработки запроса
time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond)
// Атомарно получаем текущую конфигурацию
conf := config.Load().(ServerConfig)
reqID := counter.Add(1)
fmt.Printf("Worker %d processing request #%d with config: %+v\n",
workerID, reqID, conf)
}
}
func configUpdater() {
for {
time.Sleep(5 * time.Second)
newConfig := loadConfig()
config.Store(newConfig)
fmt.Printf("\nConfig updated: %+v\n\n", newConfig)
}
}
func main() {
// Инициализация начальной конфигурации
config.Store(loadConfig())
fmt.Println("Initial config:", config.Load())
// Запускаем обновление конфигурации в фоне
go configUpdater()
// Запускаем воркеров для обработки запросов
for i := 1; i <= 5; i++ {
go processRequest(i)
}
// Даем поработать 30 секунд
time.Sleep(30 * time.Second)
fmt.Println("Server shutting down...")
}
Вывод программы:
Initial config: {Host:server-3.example.com Port:8034 Timeout:4s Debug:false}
Worker 5 processing request #1 with config: {Host:server-3.example.com Port:8034 Timeout:4s Debug:false}
Worker 3 processing request #2 with config: {Host:server-3.example.com Port:8034 Timeout:4s Debug:false}
Config updated: {Host:server-1.example.com Port:8067 Timeout:2s Debug:true}
Worker 1 processing request #3 with config: {Host:server-1.example.com Port:8067 Timeout:2s Debug:true}
Worker 4 processing request #4 with config: {Host:server-1.example.com Port:8067 Timeout:2s Debug:true}
Пример ReadMostly
package main
import (
"fmt"
"sync"
"sync/atomic"
"time"
)
// ConcurrentMap реализует потокобезопасную map с помощью atomic.Value
type ConcurrentMap struct {
data atomic.Value // хранит map[string]string
mu sync.Mutex // только для записи
}
// NewConcurrentMap создает новую потокобезопасную map
func NewConcurrentMap() *ConcurrentMap {
cm := &ConcurrentMap{}
cm.data.Store(make(map[string]string))
return cm
}
// Get безопасно получает значение по ключу
func (cm *ConcurrentMap) Get(key string) (string, bool) {
currentMap := cm.data.Load().(map[string]string)
val, ok := currentMap[key]
return val, ok
}
// Set безопасно устанавливает значение по ключу
func (cm *ConcurrentMap) Set(key, value string) {
cm.mu.Lock()
defer cm.mu.Unlock()
// Копируем текущую map
oldMap := cm.data.Load().(map[string]string)
newMap := make(map[string]string, len(oldMap)+1)
// Копируем все элементы
for k, v := range oldMap {
newMap[k] = v
}
// Добавляем/обновляем элемент
newMap[key] = value
// Атомарно заменяем map
cm.data.Store(newMap)
}
// Delete безопасно удаляет элемент по ключу
func (cm *ConcurrentMap) Delete(key string) {
cm.mu.Lock()
defer cm.mu.Unlock()
oldMap := cm.data.Load().(map[string]string)
if _, exists := oldMap[key]; !exists {
return
}
newMap := make(map[string]string, len(oldMap))
for k, v := range oldMap {
if k != key {
newMap[k] = v
}
}
cm.data.Store(newMap)
}
func main() {
config := NewConcurrentMap()
// Записываем начальные значения
config.Set("host", "localhost")
config.Set("port", "8080")
config.Set("timeout", "30s")
// Запускаем 5 читателей
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < 3; j++ {
time.Sleep(time.Millisecond * 100)
if val, ok := config.Get("host"); ok {
fmt.Printf("Reader %d: host=%s\n", id, val)
}
}
}(i)
}
// Запускаем 2 писателя
for i := 0; i < 2; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < 2; j++ {
time.Sleep(time.Millisecond * 200)
newHost := fmt.Sprintf("server-%d-%d", id, j)
config.Set("host", newHost)
fmt.Printf("Writer %d updated host to %s\n", id, newHost)
}
}(i)
}
// Запускаем удаление через некоторое время
wg.Add(1)
go func() {
defer wg.Done()
time.Sleep(time.Millisecond * 300)
config.Delete("timeout")
fmt.Println("Deleted timeout key")
}()
wg.Wait()
// Финальное состояние
fmt.Println("\nFinal config:")
if host, ok := config.Get("host"); ok {
fmt.Println("host:", host)
}
if port, ok := config.Get("port"); ok {
fmt.Println("port:", port)
}
if _, ok := config.Get("timeout"); !ok {
fmt.Println("timeout: (deleted)")
}
}
Вывод программы:
Reader 4: host=localhost
Reader 0: host=localhost
Reader 2: host=localhost
Reader 1: host=localhost
Writer 0 updated host to server-0-0
Reader 3: host=server-0-0
Reader 0: host=server-0-0
Reader 4: host=server-0-0
Writer 1 updated host to server-1-0
Reader 2: host=server-1-0
Deleted timeout key
Reader 1: host=server-1-0
Writer 0 updated host to server-0-1
Writer 1 updated host to server-1-1
Reader 3: host=server-1-1
Final config:
host: server-1-1
port: 8080
timeout: (deleted)
func (*Value) CompareAndSwap
func (v *Value) CompareAndSwap(old, new any) (swapped bool)
CompareAndSwap выполняет операцию сравнения и замены для Value.
Все вызовы CompareAndSwap для данного значения должны использовать значения одного и того же конкретного типа. CompareAndSwap несовместимого типа вызывает панику, как и CompareAndSwap(old, nil).
func (*Value) Load
func (v *Value) Load() (val any)
Load возвращает значение, установленное последним Store. Он возвращает nil, если для этого значения не было вызова Store.
func (*Value) Store
func (v *Value) Store(val any)
Store устанавливает значение Value v равным val. Все вызовы Store для данного Value должны использовать значения одного и того же конкретного типа. Store несовместимого типа вызывает панику, как и Store(nil).
func (*Value) Swap
func (v *Value) Swap(new any) (old any)
Swap сохраняет new в Value и возвращает предыдущее значение. Он возвращает nil, если Value пуст.
Все вызовы Swap для данного Value должны использовать значения одного и того же конкретного типа. Swap несовместимого типа вызывает панику, как и Swap(nil).