Czym są operacje atomowe, jakie są ich rodzaje i kiedy oraz jak ich najlepiej używać?
sobes.tech AI
Odpowiedź od AI
Operacje atomowe to operacje na pamięci, których wykonanie gwarantuje, że albo zostaną one w pełni zakończone, albo w ogóle się nie rozpoczną, i nie mogą być przerwane przez inne konkurujące operacje. Są niepodzielne i bezpieczne dla wątków na poziomie sprzętowym.
W Go, pakiet sync/atomic zapewnia zestaw funkcji do wykonywania operacji atomowych:
| Funkcja | Opis |
|---|---|
AddInt32 |
Atomowo dodaje wartość do int32. |
AddInt64 |
Atomowo dodaje wartość do int64. |
AddUint32 |
Atomowo dodaje wartość do uint32. |
AddUint64 |
Atomowo dodaje wartość do uint64. |
AddUintptr |
Atomowo dodaje wartość do uintptr. |
CompareAndSwapInt32 |
Porównuje i zamienia (CAS) int32 atomowo. |
CompareAndSwapInt64 |
Porównuje i zamienia (CAS) int64 atomowo. |
CompareAndSwapUint32 |
Porównuje i zamienia (CAS) uint32. |
CompareAndSwapUint64 |
Porównuje i zamienia (CAS) uint64. |
CompareAndSwapUintptr |
Porównuje i zamienia (CAS) uintptr. |
CompareAndSwapPointer |
Porównuje i zamienia (CAS) unsafe.Pointer. |
LoadInt32 |
Atomowo ładuje wartość int32. |
LoadInt64 |
Atomowo ładuje wartość int64. |
LoadUint32 |
Atomowo ładuje wartość uint32. |
LoadUint64 |
Atomowo ładuje wartość uint64. |
LoadUintptr |
Atomowo ładuje wartość uintptr. |
LoadPointer |
Atomowo ładuje wartość unsafe.Pointer. |
StoreInt32 |
Atomowo zapisuje wartość int32. |
StoreInt64 |
Atomowo zapisuje wartość int64. |
StoreUint32 |
Atomowo zapisuje wartość uint32. |
StoreUint64 |
Atomowo zapisuje wartość uint64. |
StoreUintptr |
Atomowo zapisuje wartość uintptr. |
StorePointer |
Atomowo zapisuje wartość unsafe.Pointer. |
SwapInt32 |
Atomowo zamienia wartości int32. |
SwapInt64 |
Atomowo zamienia wartości int64. |
SwapUint32 |
Atomowo zamienia wartości uint32. |
SwapUint64 |
Atomowo zamienia wartości uint64. |
SwapUintptr |
Atomowo zamienia wartości uintptr. |
SwapPointer |
Atomowo zamienia wartości unsafe.Pointer. |
Zaleca się używanie operacji atomowych w przypadkach, gdy wymagana jest prosta, niskopoziomowa synchronizacja dostępu do wspólnych zmiennych, najczęściej liczników lub flag, bez użycia mutexów. Zapewniają minimalne koszty i lepszą wydajność w takich scenariuszach.
Przykład użycia atomowego licznika:
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() {
// Atomowo zwiększamy licznik
atomic.AddInt64(&counter, 1)
wg.Done()
}()
}
wg.Wait()
// Atomowo ładujemy wartość licznika
fmt.Println("Ostateczny licznik:", atomic.LoadInt64(&counter))
}
Przykład użycia CompareAndSwap:
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var value int32 = 10
// Próba zamiany wartości, jeśli obecnie jest 10, na 20
swapped := atomic.CompareAndSwapInt32(&value, 10, 20)
fmt.Println("Zamiana:", swapped) // true
fmt.Println("Wartość:", value) // 20
// Próba zamiany wartości, jeśli obecnie jest 10, na 30
swapped = atomic.CompareAndSwapInt32(&value, 10, 30)
fmt.Println("Zamiana:", swapped) // false
fmt.Println("Wartość:", value) // 20
}
Ważne jest, aby pamiętać, że operacje atomowe działają tylko na typach prostych (liczbach całkowitych, wskaźnikach). Do synchronizacji dostępu do bardziej złożonych struktur danych lub wykonywania operacji blokujących nadal potrzebne są mutexy lub inne mechanizmy synchronizacji. Nadmierne lub niewłaściwe użycie operacji atomowych może prowadzić do trudnych do wykrycia błędów lub obniżenia wydajności.