sync.Cond în Go

Variabilă de condiție: goroutine-urile așteaptă cu Wait până când altul anunță o schimbare cu Signal sau Broadcast.

Sintaxă
func NewCond(l Locker) *Cond
func (c *Cond) Wait()
func (c *Cond) Signal()
func (c *Cond) Broadcast()

Ce face sync.Cond

sync.NewCond(&mu) creează o variabilă de condiție legată de un lacăt. sync.Cond leagă un lacăt (c.L, de obicei un *sync.Mutex) de un loc de așteptare. Un goroutine care are nevoie de o condiție (de exemplu „coada nu e goală”) ține lacătul și apelează c.Wait() într-o buclă for !conditie. Wait eliberează lacătul, doarme până primește semnal, apoi reia lacătul înainte să se întoarcă.

Cine schimbă starea apelează c.Signal() (trezește un singur goroutine care așteaptă) sau c.Broadcast() (îi trezește pe toți). Bucla for în jurul lui Wait este obligatorie: când un goroutine se trezește, condiția poate fi deja falsă din nou.

În cele mai multe programe Go, canalele (chan) sunt mai simple și mai greu de folosit greșit. Cond rămâne util când mulți așteaptă aceeași stare comună, protejată deja de un mutex.

Ce returnează

NewCond: *sync.Cond. Wait, Signal, Broadcast nu returnează nimic.

Exemple

Exemplul 1: Coadă cu Wait și Signal

Consumatorul așteaptă cât timp coada e goală. Producătorul adaugă o comandă și trezește consumatorul cu Signal.

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	var mu sync.Mutex
	cond := sync.NewCond(&mu)
	var coada []string
	gata := make(chan bool)

	go func() { // consumator
		for range 3 {
			mu.Lock()
			for len(coada) == 0 {
				cond.Wait()
			}
			comanda := coada[0]
			coada = coada[1:]
			mu.Unlock()
			fmt.Println("Pregătesc", comanda)
		}
		gata <- true
	}()

	for _, c := range []string{"pizza", "ciorbă", "papanași"} {
		time.Sleep(20 * time.Millisecond)
		mu.Lock()
		coada = append(coada, c)
		mu.Unlock()
		cond.Signal()
	}
	<-gata
}

Rezultat

Pregătesc pizza
Pregătesc ciorbă
Pregătesc papanași

Exemplul 2: Start comun pentru toți cu Broadcast

Cinci alergători așteaptă semnalul de start; Broadcast îi trezește pe toți deodată.

package main

import (
	"fmt"
	"sync"
)

func main() {
	var mu sync.Mutex
	cond := sync.NewCond(&mu)
	start := false
	var pregatiti, porniti sync.WaitGroup
	var plecati []int

	for i := 1; i <= 5; i++ {
		pregatiti.Add(1)
		porniti.Add(1)
		go func() {
			defer porniti.Done()
			mu.Lock()
			pregatiti.Done()
			for !start {
				cond.Wait()
			}
			plecati = append(plecati, i)
			mu.Unlock()
		}()
	}

	pregatiti.Wait()
	mu.Lock()
	start = true
	mu.Unlock()
	fmt.Println("Start!")
	cond.Broadcast()

	porniti.Wait()
	fmt.Println("Au plecat", len(plecati), "alergători")
}

Rezultat

Start!
Au plecat 5 alergători

Greșeli frecvente

  • Apelezi Wait fără să ții lacătul (c.L.Lock()): programul se oprește cu eroare.
  • Scrii if !conditie { c.Wait() } în loc de for. După trezire condiția poate fi din nou falsă (alt goroutine a luat elementul).
  • Apelezi Signal înainte ca cel care așteaptă să ajungă la Wait și nu ai o variabilă de stare: semnalul se pierde. Starea (start, coada) trebuie să existe separat de semnal.

Comenzi înrudite

  • sync.Mutex: Lacăt de excludere reciprocă: doar un goroutine pe rând poate rula codul dintre Lock() și Unlock().
  • sync.WaitGroup: Așteaptă ca un grup de goroutine să termine: Add mărește contorul, Done îl scade, Wait blochează până ajunge la zero.
  • context.Context: Interfața care duce prin program semnalul de anulare, termenul limită și valori legate de o cerere (ctx).