Menu
Coddy logo textTech

sync.WaitGroup

Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 64 z 107.

W lekcji Podstawy goroutines użyliśmy time.Sleep, aby poczekać na zakończenie goroutines — to zawodne podejście. sync.WaitGroup zapewnia właściwy sposób oczekiwania na wykonanie pracy przez grupę goroutines.

WaitGroup działa jak licznik. Zwiększasz jego wartość przed uruchomieniem gorutyny, zmniejszasz ją, gdy gorutyna zakończy działanie, i blokujesz wykonanie, aż licznik osiągnie zero:

func main() {
    var wg sync.WaitGroup
    
    for i := 1; i <= 3; i++ {
        wg.Add(1)  // zwiększ licznik
        go func(id int) {
            defer wg.Done()  // zmniejsz licznik po zakończeniu
            fmt.Printf("Worker %d finished\n", id)
        }(i)
    }
    
    wg.Wait()  // czekaj, aż licznik osiągnie 0
    fmt.Println("All workers complete")
}

Trzy kluczowe metody to Add(n), która zwiększa licznik o n, Done(), która zmniejsza go o jeden (równoważna Add(-1)), oraz Wait(), która blokuje działanie do chwili, gdy licznik osiągnie zero. Użycie defer wg.Done() zapewnia zmniejszenie licznika nawet wtedy, gdy gorutyna wywoła panikę.

Częstym błędem jest wywoływanie Add wewnątrz gorutyny zamiast przed jej uruchomieniem. Powoduje to wyścig, w którym Wait może zakończyć działanie, zanim wszystkie gorutyny zostaną zarejestrowane:

// Niepoprawnie – wyścig danych
go func() {
    wg.Add(1)  // może wykonać się po Wait()
    defer wg.Done()
}()

// Poprawnie – Add przed uruchomieniem
wg.Add(1)
go func() {
    defer wg.Done()
}()

WaitGroupy są często osadzane w strukturach, aby koordynować współbieżne operacje, dzięki czemu są niezbędne do tworzenia bezpiecznych wątkowo typów w Go.

challenge icon

Wyzwanie

Łatwy

Zbudujmy menedżera pobierania, który koordynuje wiele równoczesnych pobrań plików za pomocą sync.WaitGroup. Menedżer będzie śledzić, kiedy wszystkie pobrania się zakończą, bez polegania na arbitralnych timerach uśpienia.

Podzielisz kod na dwa pliki:

  • downloader.go: Zdefiniuj logikę koordynującą pobieranie.

    Utwórz strukturę Download z polami ID (int), Filename (string) i Size (int, oznaczającym rozmiar w KB).

    Zaimplementuj metodę Process(wg *sync.WaitGroup) dla Download, która symuluje pobieranie, usypiając na czas zależny od rozmiaru pliku (użyj Size * 10 milisekund). Po zakończeniu pobierania wypisz: Downloaded: [Filename] ([Size]KB). Użyj defer wg.Done(), aby licznik WaitGroup został prawidłowo zmniejszony.

    Utwórz funkcję StartDownloads(downloads []Download) int, która uruchamia wszystkie pobrania równocześnie za pomocą gorutyn i czeka na ich zakończenie. Pamiętaj, aby wywołać wg.Add(1) przed uruchomieniem każdej gorutyny, a nie wewnątrz niej. Zwróć łączny rozmiar wszystkich pobrań.

  • main.go: Wczytaj informacje o pobieranych plikach i zorganizuj równoczesne pobieranie.

    Wczytaj liczbę pobrań, a następnie dla każdego pobrania wczytaj jego ID, nazwę pliku i rozmiar. Utwórz pobrania i przekaż je do StartDownloads. Po zakończeniu wszystkich pobrań wypisz: All downloads complete: [total]KB total

Dane wejściowe będą miały następującą postać:

  • Wiersz 1: Liczba pobrań (liczba całkowita)
  • Kolejne wiersze: Dla każdego pobrania trzy wiersze — ID (liczba całkowita), nazwa pliku (ciąg znaków) i rozmiar w KB (liczba całkowita)

Na przykład dla danych:

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

Wynik powinien pokazywać kończące się pobrania (mniejsze pliki kończą się jako pierwsze ze względu na krótszy czas uśpienia), a następnie podsumowanie:

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

Główna różnica w porównaniu z użyciem time.Sleep polega na tym, że wg.Wait() blokuje działanie do chwili, gdy wszystkie gorutyny zasygnalizują zakończenie za pomocą Done(), zapewniając precyzyjną synchronizację niezależnie od czasu trwania każdego pobrania.

Spróbuj swoich sił

package main

import (
	"fmt"
)

func main() {
	// Odczytaj liczbę pobrań
	var n int
	fmt.Scanln(&n)

	// Odczytaj informacje o pobraniach
	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: Wywołaj StartDownloads z wycinkiem downloads
	// TODO: Wyświetl podsumowanie: "All downloads complete: [total]KB total"
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Go online