Menu
Coddy logo textTech

sync.WaitGroup

Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 64 de 107.

Na lição de Fundamentos de Goroutines, usamos time.Sleep para esperar que as goroutines terminassem — uma abordagem frágil. sync.WaitGroup fornece uma maneira adequada de esperar que uma coleção de goroutines complete seu trabalho.

Um WaitGroup funciona como um contador. Você o incrementa antes de iniciar uma goroutine, o decrementa quando a goroutine termina e bloqueia até que o contador chegue a zero:

func main() {
    var wg sync.WaitGroup
    
    for i := 1; i <= 3; i++ {
        wg.Add(1)  // incrementa o contador
        go func(id int) {
            defer wg.Done()  // decrementa ao finalizar
            fmt.Printf("Worker %d finished\n", id)
        }(i)
    }
    
    wg.Wait()  // bloqueia até que o contador seja 0
    fmt.Println("All workers complete")
}

Os três métodos principais são Add(n) para aumentar o contador em n, Done() para diminuí-lo em um (equivalente a Add(-1)), e Wait() para bloquear até que o contador chegue a zero. O uso de defer wg.Done() garante que o contador decremente mesmo se a goroutine entrar em pânico.

Um erro comum é chamar Add dentro da goroutine em vez de antes dela. Isso cria uma condição de corrida onde Wait pode retornar antes que todas as goroutines sejam registradas:

// Errado - condição de corrida
go func() {
    wg.Add(1)  // pode ser executado após Wait()
    defer wg.Done()
}()

// Correto - Adicione antes de iniciar
wg.Add(1)
go func() {
    defer wg.Done()
}()

WaitGroups são frequentemente incorporados em structs para coordenar operações concorrentes, tornando-os essenciais para a construção de tipos thread-safe em Go.

challenge icon

Desafio

Fácil

Vamos construir um gerenciador de downloads que coordena múltiplos downloads de arquivos simultâneos usando sync.WaitGroup. Seu gerenciador rastreará quando todos os downloads forem concluídos sem depender de temporizadores de espera arbitrários.

Você organizará seu código em dois arquivos:

  • downloader.go: Defina sua lógica de coordenação de download.

    Crie uma struct Download com os campos ID (int), Filename (string) e Size (int, representando KB).

    Implemente um método Process(wg *sync.WaitGroup) em Download que simula o download aguardando por uma duração baseada no tamanho do arquivo (use Size * 10 milissegundos). Quando o download for concluído, imprima: Downloaded: [Filename] ([Size]KB). Use defer wg.Done() para garantir que o contador do WaitGroup decremente corretamente.

    Crie uma função StartDownloads(downloads []Download) int que inicia todos os downloads simultaneamente usando goroutines e espera que todos sejam concluídos. Lembre-se de chamar wg.Add(1) antes de iniciar cada goroutine, não dentro dela. Retorne o tamanho total de todos os downloads combinados.

  • main.go: Leia as informações de download e orquestre os downloads simultâneos.

    Leia o número de downloads e, para cada download, leia seu ID, nome do arquivo e tamanho. Crie os downloads e passe-os para StartDownloads. Após a conclusão de todos os downloads, imprima: All downloads complete: [total]KB total

As seguintes entradas serão fornecidas:

  • Linha 1: Número de downloads (inteiro)
  • Linhas seguintes: Para cada download, três linhas - o ID (inteiro), nome do arquivo (string) e tamanho em KB (inteiro)

Por exemplo, dado:

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

Sua saída deve mostrar os downloads sendo concluídos (arquivos menores terminam primeiro devido aos tempos de espera mais curtos), seguidos pelo resumo:

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

A principal diferença em relação ao uso de time.Sleep é que wg.Wait() bloqueia até que todas as goroutines sinalizem a conclusão com Done(), proporcionando uma sincronização precisa, independentemente de quanto tempo cada download leve.

Folha de consulta

Um sync.WaitGroup fornece uma maneira adequada de esperar que as goroutines sejam concluídas. Ele funciona como um contador que rastreia as goroutines em execução.

Os três métodos principais são:

  • Add(n) - aumenta o contador em n
  • Done() - diminui o contador em um (equivalente a Add(-1))
  • Wait() - bloqueia até que o contador chegue a zero

Padrão de uso básico:

var wg sync.WaitGroup

for i := 1; i <= 3; i++ {
    wg.Add(1)  // incrementa antes de iniciar a goroutine
    go func(id int) {
        defer wg.Done()  // decrementa quando terminar
        fmt.Printf("Worker %d finished\n", id)
    }(i)
}

wg.Wait()  // bloqueia até que todas as goroutines terminem
fmt.Println("All workers complete")

O uso de defer wg.Done() garante que o contador seja decrementado mesmo se a goroutine entrar em pânico (panic).

Importante: Sempre chame Add antes de iniciar a goroutine, não dentro dela, para evitar condições de corrida (race conditions):

// Errado - condição de corrida
go func() {
    wg.Add(1)  // pode ser executado após Wait()
    defer wg.Done()
}()

// Correto
wg.Add(1)
go func() {
    defer wg.Done()
}()

Experimente você mesmo

package main

import (
	"fmt"
)

func main() {
	// Ler o número de downloads
	var n int
	fmt.Scanln(&n)

	// Ler informações de 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: Chamar StartDownloads com o slice de downloads
	// TODO: Imprimir o resumo: "All downloads complete: [total]KB total"
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos