sync/atomic.Value în Go

Ține o valoare de orice tip, înlocuită și citită atomic; folosită pentru configurări schimbate „din mers”.

Sintaxă
var v atomic.Value
func (v *Value) Load() (val any)
func (v *Value) Store(val any)
func (v *Value) Swap(new any) (old any)               // Go 1.17
func (v *Value) CompareAndSwap(old, new any) bool      // Go 1.17

Ce face sync/atomic.Value

atomic.Value păstrează o valoare de tip any. Store o înlocuiește dintr-o dată, iar Load returnează ultima valoare salvată (sau nil dacă nu s-a salvat nimic). Cititorii nu se blochează niciodată, deci este potrivit pentru date citite foarte des și schimbate rar, de exemplu o configurare reîncărcată fără oprirea serverului.

Toate valorile salvate trebuie să aibă același tip concret; altfel Store se oprește cu panică. Store(nil) nu este permis. Valoarea salvată nu trebuie modificată după Store: creezi o valoare nouă și o salvezi pe ea.

Din Go 1.19, atomic.Pointer[T] face același lucru cu tipuri verificate de compilator și este de preferat în cod nou.

Ce returnează

Load: valoarea (any) sau nil. Swap: valoarea veche. CompareAndSwap: dacă a înlocuit.

Exemple

Exemplul 1: Configurare reîncărcată din mers

package main

import (
	"fmt"
	"sync/atomic"
)

type Config struct {
	Mesaj   string
	Limita  int
}

var config atomic.Value

func main() {
	fmt.Println("Înainte de Store:", config.Load())

	config.Store(Config{Mesaj: "Bun venit!", Limita: 100})
	c := config.Load().(Config)
	fmt.Println("Configurare:", c.Mesaj, c.Limita)

	// reîncărcare: valoare nouă, salvată dintr-o dată
	config.Store(Config{Mesaj: "Reduceri de Black Friday!", Limita: 500})
	c = config.Load().(Config)
	fmt.Println("După reîncărcare:", c.Mesaj, c.Limita)
}

Rezultat

Înainte de Store: <nil>
Configurare: Bun venit! 100
După reîncărcare: Reduceri de Black Friday! 500

Exemplul 2: Tipul trebuie să rămână același

Un Store cu alt tip se oprește cu panică; o recuperăm ca să vedem mesajul.

package main

import (
	"fmt"
	"sync/atomic"
)

func main() {
	var v atomic.Value
	v.Store("Iași")
	vechi := v.Swap("Cluj")
	fmt.Println("Swap:", vechi, "->", v.Load())

	defer func() {
		fmt.Println("Panică:", recover())
	}()
	v.Store(42)
}

Rezultat

Swap: Iași -> Cluj
Panică: sync/atomic: store of inconsistently typed value into Value

Greșeli frecvente

  • Salvezi un map sau un slice și apoi îl modifici. Cititorii văd modificările fără sincronizare; construiește un map nou și salvează-l pe el.
  • Faci v.Load().(Config) înainte de primul Store: Load returnează nil și conversia se oprește cu panică. Salvează o valoare inițială sau folosește c, ok := v.Load().(Config).

Comenzi înrudite

  • sync/atomic.Pointer: Un pointer *T citit și înlocuit atomic, cu tip verificat la compilare; varianta modernă a lui atomic.Value.
  • sync.RWMutex: Lacăt pentru citire și scriere: mai mulți cititori pot lucra deodată (RLock), dar un scriitor lucrează singur (Lock).
  • sync.Once: Garantează că o funcție rulează o singură dată, chiar dacă Do este apelat de multe goroutine (inițializare leneșă).