sync.Once în Go

Garantează că o funcție rulează o singură dată, chiar dacă Do este apelat de multe goroutine (inițializare leneșă).

Sintaxă
var once sync.Once
func (o *Once) Do(f func())

Ce face sync.Once

once.Do(f) rulează f doar la primul apel; toate apelurile următoare nu mai fac nimic. Dacă mai multe goroutine apelează Do în același timp, una rulează f, iar celelalte așteaptă să termine, deci după Do inițializarea este sigur gata.

Se folosește pentru inițializări costisitoare făcute doar când e nevoie: conexiunea la baza de date, încărcarea unei configurări, compilarea unor expresii regulate. Pentru variantele care returnează o valoare, vezi sync.OnceValue și sync.OnceFunc.

Dacă f se oprește cu panică, Once consideră totuși că a rulat; apelurile următoare nu mai încearcă.

Parametri

Parametru Descriere
f Funcția de inițializare, de forma func().

Ce returnează

Nimic.

Exemple

Exemplul 1: Configurare încărcată o singură dată

Zece goroutine cer configurarea; mesajul „Încarc configurarea” apare o singură dată.

package main

import (
	"fmt"
	"sync"
)

var (
	once  sync.Once
	setari map[string]string
)

func configurare() map[string]string {
	once.Do(func() {
		fmt.Println("Încarc configurarea...")
		setari = map[string]string{"oras": "Iași", "moneda": "lei"}
	})
	return setari
}

func main() {
	var wg sync.WaitGroup
	for range 10 {
		wg.Go(func() {
			_ = configurare()["oras"]
		})
	}
	wg.Wait()
	fmt.Println("Oraș:", configurare()["oras"], "- monedă:", configurare()["moneda"])
}

Rezultat

Încarc configurarea...
Oraș: Iași - monedă: lei

Exemplul 2: Once într-o structură

Fiecare obiect are propriul Once, deci se inițializează separat.

package main

import (
	"fmt"
	"strings"
	"sync"
)

type Document struct {
	text     string
	once     sync.Once
	cuvinte  int
}

func (d *Document) NumarCuvinte() int {
	d.once.Do(func() {
		fmt.Println("  (număr cuvintele o dată)")
		d.cuvinte = len(strings.Fields(d.text))
	})
	return d.cuvinte
}

func main() {
	d := &Document{text: "Go este un limbaj simplu și rapid"}
	fmt.Println("Cuvinte:", d.NumarCuvinte())
	fmt.Println("Cuvinte:", d.NumarCuvinte())
	alt := &Document{text: "Salut Iași"}
	fmt.Println("Alt document:", alt.NumarCuvinte())
}

Rezultat

  (număr cuvintele o dată)
Cuvinte: 7
Cuvinte: 7
  (număr cuvintele o dată)
Alt document: 2

Greșeli frecvente

  • Apelezi once.Do din interiorul funcției date aceluiași once.Do: programul se blochează (Do așteaptă propria funcție).
  • Te aștepți ca Once să reîncerce după o eroare. Dacă inițializarea poate eșua și vrei să reîncerci, folosește un mutex și o variabilă proprie.
  • Folosești if !initializat { ... } cu un bool obișnuit. Între goroutine este o cursă de date; sync.Once rezolvă corect cazul.

Comenzi înrudite

  • sync.OnceFunc: Returnează o funcție care rulează f doar la primul apel; apelurile următoare nu fac nimic.
  • sync.OnceValue: Returnează o funcție care calculează valoarea o singură dată și o dă înapoi la fiecare apel; OnceValues pentru două valori.
  • sync.Mutex: Lacăt de excludere reciprocă: doar un goroutine pe rând poate rula codul dintre Lock() și Unlock().