Menu
Coddy logo textTech

sync.WaitGroup

Fa parte della sezione Programmazione orientata agli oggetti del percorso GO di Coddy. Lezione 64 di 107.

Nella lezione Nozioni di base sulle Goroutine, abbiamo usato time.Sleep per aspettare che le goroutine terminassero: un approccio fragile. sync.WaitGroup offre un modo adeguato per aspettare che un gruppo di goroutine completi il proprio lavoro.

Un WaitGroup funziona come un contatore. Lo incrementi prima di avviare una goroutine, lo decrementi quando la goroutine termina e blocchi l'esecuzione finché il contatore non raggiunge zero:

func main() {
    var wg sync.WaitGroup
    
    for i := 1; i <= 3; i++ {
        wg.Add(1)  // incrementa il contatore
        go func(id int) {
            defer wg.Done()  // decrementa al termine
            fmt.Printf("Worker %d finished\n", id)
        }(i)
    }
    
    wg.Wait()  // blocca finché il contatore non è 0
    fmt.Println("All workers complete")
}

I tre metodi chiave sono Add(n) per aumentare il contatore di n, Done() per diminuirlo di uno (equivalente a Add(-1)) e Wait() per bloccare l’esecuzione finché il contatore non raggiunge zero. Usare defer wg.Done() garantisce che il contatore venga decrementato anche se la goroutine va in panic.

Un errore comune è chiamare Add all'interno della goroutine invece che prima. Questo crea una condizione di race in cui Wait potrebbe restituire prima che tutte le goroutine siano registrate:

// Sbagliato - condizione di competizione
go func() {
    wg.Add(1)  // potrebbe essere eseguito dopo Wait()
    defer wg.Done()
}()

// Corretto - Add prima dell'avvio
wg.Add(1)
go func() {
    defer wg.Done()
}()

Spesso i WaitGroup sono incorporati nelle struct per coordinare operazioni concorrenti, rendendoli essenziali per creare tipi thread-safe in Go.

challenge icon

Sfida

Facile

Costruiamo un gestore di download che coordina più download di file simultanei usando sync.WaitGroup. Il gestore terrà traccia del completamento di tutti i download senza affidarsi a timer di attesa arbitrari.

Organizzerai il codice in due file:

  • downloader.go: Definisci la logica di coordinamento dei download.

    Crea una struct Download con i campi ID (int), Filename (string) e Size (int, che rappresenta i KB).

    Implementa su Download un metodo Process(wg *sync.WaitGroup) che simula il download sospendendo l'esecuzione per una durata basata sulla dimensione del file (usa Size * 10 millisecondi). Quando il download è completato, stampa: Downloaded: [Filename] ([Size]KB). Usa defer wg.Done() per assicurarti che il contatore del WaitGroup venga decrementato correttamente.

    Crea una funzione StartDownloads(downloads []Download) int che avvia tutti i download simultaneamente usando le goroutine e attende che siano tutti completati. Ricorda di chiamare wg.Add(1) prima di avviare ciascuna goroutine, non al suo interno. Restituisci la dimensione totale di tutti i download.

  • main.go: Leggi le informazioni sui download e coordina i download simultanei.

    Leggi il numero di download, poi per ciascun download leggi il suo ID, il nome del file e la dimensione. Crea i download e passali a StartDownloads. Dopo il completamento di tutti i download, stampa: All downloads complete: [total]KB total

Verranno forniti i seguenti input:

  • Riga 1: Numero di download (intero)
  • Righe successive: Per ciascun download, tre righe: l'ID (intero), il nome del file (stringa) e la dimensione in KB (intero)

Per esempio, dato:

3
1
report.pdf
20
2
image.png
5
3
data.csv
10

Il tuo output dovrebbe mostrare il completamento dei download (i file più piccoli terminano prima grazie ai tempi di attesa più brevi), seguito dal riepilogo:

Downloaded: image.png (5KB)
Downloaded: data.csv (10KB)
Downloaded: report.pdf (20KB)
All downloads complete: 35KB total

La differenza fondamentale rispetto all'uso di time.Sleep è che wg.Wait() blocca l'esecuzione finché tutte le goroutine non segnalano il completamento con Done(), garantendo una sincronizzazione precisa indipendentemente dalla durata di ciascun download.

Provalo tu

package main

import (
	"fmt"
)

func main() {
	// Leggi il numero di download
	var n int
	fmt.Scanln(&n)

	// Leggi le informazioni sui download
	downloads := make([]Download, n)
	for i := 0; i < n; i++ {
		var id int
		var filename string
		var size int
		fmt.Scanln(&id)
		fmt.Scanln(&filename)
		fmt.Scanln(&size)
		
		downloads[i] = Download{
			ID:       id,
			Filename: filename,
			Size:     size,
		}
	}

	// TODO: Chiama StartDownloads con la slice downloads
	// TODO: Stampa il riepilogo: "All downloads complete: [total]KB total"
}
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Compilatore Go online