sync/atomic.AddInt64 în Go

Adună (AddInt64), citește (LoadInt64) și scrie (StoreInt64) un int64 atomic, sigur între goroutine, fără mutex.

Sintaxă
import "sync/atomic"

func AddInt64(addr *int64, delta int64) (new int64)
func LoadInt64(addr *int64) (val int64)
func StoreInt64(addr *int64, val int64)

Ce face sync/atomic.AddInt64

O operație atomică se face „dintr-o bucată”: niciun alt goroutine nu poate vedea o valoare pe jumătate scrisă și nicio adunare nu se pierde. atomic.AddInt64(&n, 1) mărește n cu 1 și returnează valoarea nouă; cu delta negativ scade. Pentru un simplu contor este mai rapidă decât un sync.Mutex.

Regula: dacă o variabilă este modificată atomic, toate citirile și scrierile ei trebuie să fie atomice. Citești cu atomic.LoadInt64(&n) și scrii cu atomic.StoreInt64(&n, v), niciodată direct cu n sau n = v cât timp alte goroutine lucrează cu ea.

Din Go 1.19, tipul atomic.Int64 (cu metodele Add, Load, Store) este varianta recomandată: nu poți uita să folosești operațiile atomice, iar alinierea în memorie este garantată. Există funcții la fel pentru int32, uint32, uint64 și uintptr.

Parametri

Parametru Descriere
addr Adresa variabilei (&n).
delta Cât se adună (negativ pentru scădere).
val Valoarea scrisă de StoreInt64.

Ce returnează

AddInt64: valoarea nouă. LoadInt64: valoarea curentă. StoreInt64: nimic.

Exemple

Exemplul 1: Contor de vizite

package main

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

func main() {
	var vizite int64
	var wg sync.WaitGroup
	for range 200 {
		wg.Go(func() {
			atomic.AddInt64(&vizite, 1)
		})
	}
	wg.Wait()
	fmt.Println("Vizite:", atomic.LoadInt64(&vizite))

	fmt.Println("După încă 5:", atomic.AddInt64(&vizite, 5))
	fmt.Println("După -10:   ", atomic.AddInt64(&vizite, -10))
	atomic.StoreInt64(&vizite, 0)
	fmt.Println("Resetat:    ", atomic.LoadInt64(&vizite))
}

Rezultat

Vizite: 200
După încă 5: 205
După -10:    195
Resetat:     0

Exemplul 2: Octeți descărcați de mai mulți lucrători

package main

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

func main() {
	marimi := []int64{1_048_576, 524_288, 2_097_152, 262_144}
	var total int64
	var wg sync.WaitGroup
	for _, m := range marimi {
		wg.Go(func() {
			for range 4 { // fiecare fișier vine în 4 bucăți
				atomic.AddInt64(&total, m/4)
			}
		})
	}
	wg.Wait()
	fmt.Printf("Descărcat: %d octeți (%.2f MB)\n", atomic.LoadInt64(&total), float64(atomic.LoadInt64(&total))/1024/1024)
}

Rezultat

Descărcat: 3932160 octeți (3.75 MB)

Greșeli frecvente

  • Amesteci accesul atomic cu cel obișnuit: atomic.AddInt64(&n, 1) într-un goroutine și fmt.Println(n) în altul, în același timp. Citirea directă este o cursă de date; folosește LoadInt64.
  • Pe sisteme pe 32 de biți, un int64 folosit atomic într-o structură trebuie să fie aliniat la 8 octeți, altfel programul se oprește. Tipul atomic.Int64 rezolvă singur problema.

Comenzi înrudite

  • sync/atomic.Int64: Un int64 cu operații atomice ca metode (Add, Load, Store, Swap); varianta recomandată pentru contoare între goroutine.
  • sync/atomic.CompareAndSwapInt64: CompareAndSwapInt64 scrie doar dacă valoarea e cea așteptată; SwapInt64 scrie și returnează valoarea veche.
  • sync.Mutex: Lacăt de excludere reciprocă: doar un goroutine pe rând poate rula codul dintre Lock() și Unlock().