sync/atomic.Int64 în Go

Un int64 cu operații atomice ca metode (Add, Load, Store, Swap); varianta recomandată pentru contoare între goroutine.

Sintaxă
var n atomic.Int64
func (x *Int64) Add(delta int64) (new int64)
func (x *Int64) Load() int64
func (x *Int64) Store(val int64)
func (x *Int64) Swap(new int64) (old int64)

Ce face sync/atomic.Int64

atomic.Int64 este o structură care ține un int64 și permite doar acces atomic la el. Valoarea zero înseamnă 0, deci nu trebuie inițializat. n.Add(d) adună și returnează valoarea nouă, n.Load() citește, n.Store(v) scrie, iar n.Swap(v) scrie și returnează valoarea veche.

Față de funcțiile atomic.AddInt64 și celelalte, tipul are două avantaje: nu poți citi sau scrie valoarea direct din greșeală și este aliniat corect în memorie pe orice sistem. Se pune ușor într-o structură, ca în al doilea exemplu.

Ca și mutexul, nu trebuie copiat după prima folosire: structurile care îl conțin se transmit prin pointer.

Ce returnează

Add: valoarea nouă. Load: valoarea curentă. Swap: valoarea veche. Store: nimic.

Exemple

Exemplul 1: Toate cele patru metode

package main

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

func main() {
	var bilete atomic.Int64
	bilete.Store(500)

	var wg sync.WaitGroup
	for range 120 {
		wg.Go(func() {
			bilete.Add(-1) // un bilet vândut
		})
	}
	wg.Wait()
	fmt.Println("Bilete rămase:", bilete.Load())
	fmt.Println("Adaug 20:", bilete.Add(20))
	vechi := bilete.Swap(1000)
	fmt.Println("Swap: vechi =", vechi, "nou =", bilete.Load())
}

Rezultat

Bilete rămase: 380
Adaug 20: 400
Swap: vechi = 400 nou = 1000

Exemplul 2: Statistici într-o structură

package main

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

type Statistici struct {
	cereri atomic.Int64
	erori  atomic.Int64
	octeti atomic.Int64
}

func (s *Statistici) Inregistreaza(marime int64, ok bool) {
	s.cereri.Add(1)
	s.octeti.Add(marime)
	if !ok {
		s.erori.Add(1)
	}
}

func main() {
	var st Statistici
	var wg sync.WaitGroup
	for i := range 100 {
		wg.Go(func() {
			st.Inregistreaza(int64(1000+i), i%25 != 0)
		})
	}
	wg.Wait()
	fmt.Println("Cereri:", st.cereri.Load())
	fmt.Println("Erori: ", st.erori.Load())
	fmt.Println("Octeți:", st.octeti.Load())
}

Rezultat

Cereri: 100
Erori:  4
Octeți: 104950

Greșeli frecvente

  • Copiezi structura care conține atomic.Int64 (receptor valoare sau atribuire). go vet avertizează; copia are propria valoare și modificările se pierd.
  • Faci if n.Load() > 0 { n.Add(-1) } ca să nu scazi sub zero. Între cele două apeluri alt goroutine poate scădea; folosește o buclă cu CompareAndSwap.

Comenzi înrudite

  • sync/atomic.Int64.CompareAndSwap: Scrie valoarea nouă doar dacă valoarea curentă este cea așteptată; baza operațiilor atomice proprii, cu reîncercare.
  • sync/atomic.Int64.And: Aplică atomic un ȘI (And) sau un SAU (Or) pe biți; folosit pentru steaguri (flags) partajate între goroutine.
  • sync/atomic.Int32: Un int32 cu operații atomice (Add, Load, Store, Swap, CompareAndSwap); la fel ca atomic.Int64, dar pe 32 de biți.
  • sync/atomic.AddInt64: Adună (AddInt64), citește (LoadInt64) și scrie (StoreInt64) un int64 atomic, sigur între goroutine, fără mutex.
  • sync.Mutex: Lacăt de excludere reciprocă: doar un goroutine pe rând poate rula codul dintre Lock() și Unlock().