sync/atomic.Pointer în Go

Un pointer *T citit și înlocuit atomic, cu tip verificat la compilare; varianta modernă a lui atomic.Value.

Sintaxă
var p atomic.Pointer[T]
func (x *Pointer[T]) Load() *T
func (x *Pointer[T]) Store(val *T)
func (x *Pointer[T]) Swap(new *T) (old *T)
func (x *Pointer[T]) CompareAndSwap(old, new *T) (swapped bool)

Ce face sync/atomic.Pointer

atomic.Pointer[T] este un tip generic: atomic.Pointer[Config] ține un *Config. Valoarea zero este un pointer nil. Store(p) publică un obiect nou, Load() returnează pointerul curent, Swap îl înlocuiește și îl dă pe cel vechi, iar CompareAndSwap înlocuiește doar dacă pointerul curent este cel așteptat.

Modelul obișnuit este „copiază, modifică, publică”: cititorii fac Load() și lucrează cu un obiect care nu se mai schimbă, iar cine actualizează construiește un obiect nou și îl publică cu Store. Față de atomic.Value nu ai nevoie de conversii și nu există panici din cauza tipului.

Ce returnează

Load: *T (poate fi nil). Swap: pointerul vechi. CompareAndSwap: bool.

Exemple

Exemplul 1: Curs valutar actualizat din mers

package main

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

type Curs struct {
	EUR, USD float64
}

func main() {
	var curs atomic.Pointer[Curs]
	fmt.Println("Înainte de Store:", curs.Load())
	curs.Store(&Curs{EUR: 4.97, USD: 4.58})

	var wg sync.WaitGroup
	sume := make([]float64, 3)
	for i, eur := range []float64{10, 25, 100} {
		wg.Go(func() {
			c := curs.Load() // obiectul nu se mai schimbă după publicare
			sume[i] = eur * c.EUR
		})
	}
	wg.Wait()
	fmt.Printf("În lei: %.2f\n", sume)

	vechi := curs.Swap(&Curs{EUR: 4.98, USD: 4.61})
	fmt.Println("Vechi:", *vechi, "nou:", *curs.Load())
}

Rezultat

Înainte de Store: <nil>
În lei: [49.70 124.25 497.00]
Vechi: {4.97 4.58} nou: {4.98 4.61}

Exemplul 2: Actualizare cu CompareAndSwap

Copiem obiectul curent, îl modificăm și îl publicăm doar dacă nu l-a înlocuit altcineva între timp.

package main

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

type Lista struct {
	Nume []string
}

func adauga(p *atomic.Pointer[Lista], nume string) {
	for {
		vechi := p.Load()
		nou := &Lista{Nume: append(append([]string{}, vechi.Nume...), nume)}
		if p.CompareAndSwap(vechi, nou) {
			return
		}
	}
}

func main() {
	var invitati atomic.Pointer[Lista]
	invitati.Store(&Lista{})
	var wg sync.WaitGroup
	for _, n := range []string{"Ana", "Bogdan", "Carmen", "Dan"} {
		wg.Go(func() { adauga(&invitati, n) })
	}
	wg.Wait()
	fmt.Println("Invitați:", len(invitati.Load().Nume))
}

Rezultat

Invitați: 4

Greșeli frecvente

  • Modifici obiectul după ce l-ai publicat cu Store (curs.Load().EUR = 5). Alte goroutine îl citesc în același timp: cursă de date. Creează un obiect nou.
  • Folosești rezultatul lui Load() fără să verifici nil înainte de primul Store.

Comenzi înrudite

  • sync/atomic.Value: Ține o valoare de orice tip, înlocuită și citită atomic; folosită pentru configurări schimbate „din mers”.
  • sync.RWMutex: Lacăt pentru citire și scriere: mai mulți cititori pot lucra deodată (RLock), dar un scriitor lucrează singur (Lock).
  • sync/atomic.Int64: Un int64 cu operații atomice ca metode (Add, Load, Store, Swap); varianta recomandată pentru contoare între goroutine.