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.
Sfida
FacileCostruiamo 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
Downloadcon i campiID(int),Filename(string) eSize(int, che rappresenta i KB).Implementa su
Downloadun metodoProcess(wg *sync.WaitGroup)che simula il download sospendendo l'esecuzione per una durata basata sulla dimensione del file (usaSize * 10millisecondi). Quando il download è completato, stampa:Downloaded: [Filename] ([Size]KB). Usadefer wg.Done()per assicurarti che il contatore del WaitGroup venga decrementato correttamente.Crea una funzione
StartDownloads(downloads []Download) intche avvia tutti i download simultaneamente usando le goroutine e attende che siano tutti completati. Ricorda di chiamarewg.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
10Il 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 totalLa 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"
}
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
1Fondamenti di OOP in Go
File esterniWorkspace e moduli GoPacchetti e importazioniNomi esportati e non esportatiIntroduzione all’OOP in GoGli struct come classiDefinire metodi sugli structReceiver con puntatore vs receiver con valoreInizializzazione degli structFunzioni costruttriciRiepilogo - Calcolatrice semplice4Interfacce
Introduzione alle interfacceImplementazione implicitaL’interfaccia come contrattoInterfaccia vuota (any)Asserzione di tipoSwitch sul tipoComposizione di interfacceInterfacce Stringer ed ErrorRipasso - Calcolatore di forme7Incapsulamento
Campi esportati e non esportatiIncapsulamento a livello di packageMetodi Getter e SetterNascondimento delle informazioni in GoRiepilogo - Registri degli studenti10Tipi generici (Go 1.18+)
Introduzione ai tipi genericiParametri di tipoVincoli di tipoStruct genericheSoluzione alternativa per i metodi genericiRiepilogo - Collezione generica13Modelli di progettazione Parte 1
Introduzione ai modelli di progettazioneModello SingletonModello FactoryModello Abstract FactoryModello ObserverModello Strategy2Approfondimento su tipi e struct
Tipi di base e compostiDefinizioni di tipi personalizzatiTag delle structStruct anonimeStruct annidateValori zero e predefinitiRipasso - Rubrica5Composizione invece dell'ereditarietà
Perché Go non ha l'ereditarietàFondamenti dell'embedding delle structPromozione dei metodiEmbedding di più structEmbedding e aggregazione a confrontoMascheramento dei metodi embeddedRiepilogo: gerarchia dei dipendenti8Gestione degli errori e OOP
L'interfaccia errorTipi di errore personalizzatiWrapping degli errori (fmt.Errorf)Errori sentinellaerrors.Is() ed errors.As()Panic, Defer e RecoverRiepilogo - Parser di file11Libreria standard e OOP
io.Reader e io.Writersort.InterfaceInterfaccia fmt.Stringerencoding/json con le structInterfaccia http.HandlerRipasso - modelli REST API14Modelli di progettazione – Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeMiddleware come Decorator3Puntatori e memoria
Concetti base dei puntatori in GoPuntatori a structPassaggio per valore vs per riferimentoLa funzione new()Garbage collection in GoRiepilogo - Costruttore di liste concatenate6Polimorfismo in Go
Polimorfismo tramite interfacceDuck typing in GoRegole di soddisfacimento delle interfacceCollezioni polimorficheIniezione delle dipendenzeRiepilogo - Elaboratore di pagamenti9Concorrenza e OOP
Basi delle goroutineCanali e comunicazioneCanali bufferizzati vs non bufferizzatiIstruzione selectsync.Mutex e sync.RWMutexsync.WaitGroupProgettazione di struct thread-safeRiepilogo: worker poolEsercitati da solo: Compilatore Go online