sync.Map.LoadOrStore în Go

Operații combinate fără cursă de date: LoadOrStore pune doar dacă lipsește, LoadAndDelete scoate, Swap înlocuiește.

Sintaxă
func (m *Map) LoadOrStore(key, value any) (actual any, loaded bool)
func (m *Map) LoadAndDelete(key any) (value any, loaded bool)   // Go 1.15
func (m *Map) Swap(key, value any) (previous any, loaded bool)  // Go 1.20

Ce face sync.Map.LoadOrStore

LoadOrStore(k, v): dacă cheia există, returnează valoarea existentă și loaded = true; dacă nu, salvează v și returnează v cu loaded = false. Totul se face într-un singur pas, deci dintre mai multe goroutine care încearcă aceeași cheie câștigă exact unul.

LoadAndDelete(k) șterge cheia și returnează valoarea pe care o avea (loaded spune dacă exista). E potrivit pentru „ia o sarcină din listă o singură dată”. Swap(k, v) pune valoarea nouă și returnează valoarea veche, dacă exista.

Ce returnează

Valoarea (any) și un bool care spune dacă cheia exista deja.

Exemple

Exemplul 1: Cele trei operații pe rând

package main

import (
	"fmt"
	"sync"
)

func main() {
	var sesiuni sync.Map

	v, existent := sesiuni.LoadOrStore("ana", "sesiune-1")
	fmt.Println("LoadOrStore ana:", v, existent)
	v, existent = sesiuni.LoadOrStore("ana", "sesiune-2")
	fmt.Println("LoadOrStore ana:", v, existent)

	vechi, existent := sesiuni.Swap("ana", "sesiune-3")
	fmt.Println("Swap ana:       ", vechi, existent)

	v, existent = sesiuni.LoadAndDelete("ana")
	fmt.Println("LoadAndDelete:  ", v, existent)
	v, existent = sesiuni.LoadAndDelete("ana")
	fmt.Println("Din nou:        ", v, existent)
}

Rezultat

LoadOrStore ana: sesiune-1 false
LoadOrStore ana: sesiune-1 true
Swap ana:        sesiune-1 true
LoadAndDelete:   sesiune-3 true
Din nou:         <nil> false

Exemplul 2: Un singur câștigător dintre 20 de goroutine

Toate încearcă să rezerve locul 12A; LoadOrStore garantează că exact una reușește.

package main

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

func main() {
	var locuri sync.Map
	var reusite, refuzate atomic.Int32
	var wg sync.WaitGroup
	for i := 1; i <= 20; i++ {
		wg.Go(func() {
			_, ocupat := locuri.LoadOrStore("12A", fmt.Sprintf("client %d", i))
			if ocupat {
				refuzate.Add(1)
			} else {
				reusite.Add(1)
			}
		})
	}
	wg.Wait()
	fmt.Println("Rezervări reușite:", reusite.Load())
	fmt.Println("Rezervări refuzate:", refuzate.Load())
}

Rezultat

Rezervări reușite: 1
Rezervări refuzate: 19

Greșeli frecvente

  • Creezi o valoare scumpă înainte de fiecare LoadOrStore (o conexiune, un fișier deschis), deși de cele mai multe ori cheia există deja. Fă întâi un Load și creează valoarea doar dacă lipsește, apoi LoadOrStore.

Comenzi înrudite

  • sync.Map: Un map sigur pentru goroutine fără mutex extern: Store, Load, Delete, Range și Clear pot fi apelate concurent.
  • sync.Map.CompareAndSwap: Schimbă (CompareAndSwap) sau șterge (CompareAndDelete) o cheie doar dacă valoarea ei este încă cea așteptată.
  • 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.