sync.Pool în Go

O rezervă de obiecte temporare refolosibile: Get ia unul (sau creează cu New), Put îl dă înapoi; reduce alocările.

Sintaxă
var p = sync.Pool{
    New: func() any { return new(T) },
}
func (p *Pool) Get() any
func (p *Pool) Put(x any)

Ce face sync.Pool

sync.Pool păstrează obiecte care nu mai sunt folosite, ca să poată fi refolosite în loc să fie create din nou. p.Get() returnează un obiect din rezervă; dacă rezerva este goală, apelează funcția New. După ce termini, p.Put(x) pune obiectul înapoi. Este sigur pentru folosire din multe goroutine.

Este util pentru obiecte create foarte des și folosite scurt timp, de exemplu buffere (bytes.Buffer) într-un server care răspunde la mii de cereri: mai puține alocări înseamnă mai puțină muncă pentru colectorul de memorie (GC).

Rezerva nu este un cache: obiectele din ea pot fi eliberate oricând de GC, fără să fii anunțat. Nu păstra în ea date care trebuie să supraviețuiască, nu te baza pe faptul că Get returnează un obiect pus anterior și curăță obiectul (de exemplu buf.Reset()) înainte de refolosire.

Ce returnează

Get: un obiect (any), pe care îl convertești la tipul tău. Put nu returnează nimic.

Exemple

Exemplul 1: Buffere refolosite pentru mesaje

package main

import (
	"bytes"
	"fmt"
	"sync"
)

var buffere = sync.Pool{
	New: func() any { return new(bytes.Buffer) },
}

func mesaj(nume, oras string) string {
	buf := buffere.Get().(*bytes.Buffer)
	buf.Reset() // poate conține date de la folosirea anterioară
	defer buffere.Put(buf)

	buf.WriteString("Bună, ")
	buf.WriteString(nume)
	buf.WriteString(" din ")
	buf.WriteString(oras)
	buf.WriteString("!")
	return buf.String()
}

func main() {
	fmt.Println(mesaj("Ana", "Iași"))
	fmt.Println(mesaj("Radu", "Cluj"))
	fmt.Println(mesaj("Ioana", "Timișoara"))
}

Rezultat

Bună, Ana din Iași!
Bună, Radu din Cluj!
Bună, Ioana din Timișoara!

Exemplul 2: Folosire din mai multe goroutine

Fiecare goroutine ia un slice din rezervă, îl folosește și îl pune înapoi. Rezultatele sunt adunate pe poziții, apoi afișate.

package main

import (
	"fmt"
	"strconv"
	"sync"
)

var rezerva = sync.Pool{
	New: func() any {
		s := make([]byte, 0, 64)
		return &s
	},
}

func main() {
	rezultate := make([]string, 5)
	var wg sync.WaitGroup
	for i := range 5 {
		wg.Go(func() {
			p := rezerva.Get().(*[]byte)
			b := (*p)[:0]
			b = append(b, "factura-"...)
			b = strconv.AppendInt(b, int64(2026000+i), 10)
			rezultate[i] = string(b)
			*p = b
			rezerva.Put(p)
		})
	}
	wg.Wait()
	for _, r := range rezultate {
		fmt.Println(r)
	}
}

Rezultat

factura-2026000
factura-2026001
factura-2026002
factura-2026003
factura-2026004

Greșeli frecvente

  • Uiți să golești obiectul după Get: un buffer refolosit poate conține textul cererii anterioare.
  • Folosești Pool ca un cache de date. GC poate goli rezerva oricând; datele pierdute trebuie să poată fi recreate oricând cu New.
  • Pui în rezervă slice-uri ca valori (Put(s)): conversia la any alocă memorie de fiecare dată. Pune un pointer (*[]byte), ca în al doilea exemplu.

Comenzi înrudite

  • sync.Once: Garantează că o funcție rulează o singură dată, chiar dacă Do este apelat de multe goroutine (inițializare leneșă).
  • sync.Mutex: Lacăt de excludere reciprocă: doar un goroutine pe rând poate rula codul dintre Lock() și Unlock().