Menu
Coddy logo textTech

sync.WaitGroup

Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 64 из 107.

В уроке «Основы Goroutines» мы использовали time.Sleep, чтобы дождаться завершения goroutines: это ненадёжный подход. sync.WaitGroup предоставляет правильный способ дождаться, пока коллекция goroutines завершит свою работу.

WaitGroup работает как counter. Вы выполняете increment перед запуском goroutine, decrement, когда goroutine завершает работу, и выполняете block, пока counter не достигнет нуля:

func main() {
    var wg sync.WaitGroup
    
    for i := 1; i <= 3; i++ {
        wg.Add(1)  // увеличить счётчик
        go func(id int) {
            defer wg.Done()  // уменьшить по завершении
            fmt.Printf("Worker %d finished\n", id)
        }(i)
    }
    
    wg.Wait()  // блокировать, пока счётчик не станет 0
    fmt.Println("All workers complete")
}

Три ключевых метода — это Add(n) для увеличения counter на n, Done() для уменьшения его на единицу (эквивалент Add(-1)) и Wait() для блокировки, пока counter не достигнет нуля. Использование defer wg.Done() гарантирует уменьшение counter, даже если goroutine завершится с паникой.

Распространённая ошибка — вызов Add внутри goroutine вместо before it. Это создаёт состояние гонки, при котором Wait может вернуться до того, как будут зарегистрированы all goroutines:

// Неправильно - состояние гонки
go func() {
    wg.Add(1)  // может выполниться после Wait()
    defer wg.Done()
}()

// Правильно - Add перед запуском
wg.Add(1)
go func() {
    defer wg.Done()
}()

WaitGroups часто встраиваются в структуры для координации concurrent операций, что делает их необходимыми для создания потокобезопасных типов в Go.

challenge icon

Задание

Легко

Давайте создадим менеджер загрузок, который координирует несколько concurrent загрузок file с помощью sync.WaitGroup. Ваш менеджер будет отслеживать момент, когда все downloads complete, не полагаясь на произвольные таймеры сна.

Вы организуете код в двух files:

  • downloader.go: определите логику координации downloads.

    Create структуру Download с полями ID (int), Filename (string) и Size (int, обозначает размер в KB).

    Implement метод Process(wg *sync.WaitGroup) для Download, который имитирует downloading, приостанавливая выполнение на Duration, зависящую от размера file (используйте Size * 10 миллисекунд). Когда download завершится, выведите: Downloaded: [Filename] ([Size]KB). Используйте defer wg.Done(), чтобы гарантировать корректное уменьшение counter WaitGroup.

    Create function StartDownloads(downloads []Download) int, которая concurrently запускает все downloads с помощью goroutines и ждёт, пока они All complete. Помните: вызывайте wg.Add(1) before запуска каждой goroutine, а не внутри неё. Верните общий размер всех downloads.

  • main.go: считайте information о downloads и организуйте concurrent downloads.

    Считайте число downloads, затем для каждого download считайте его ID, filename и size. Создайте downloads и передайте их в StartDownloads. После того как все downloads complete, выведите: All downloads complete: [total]KB total

Будут предоставлены следующие входные данные:

  • Строка 1: число downloads (целое число)
  • Следующие строки: для каждого download три строки — ID (целое число), filename (строка) и size в KB (целое число)

Например, если дано:

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

В вашем выводе должны отображаться завершившиеся downloads (меньшие files завершаются first из-за меньшего времени сна), за которыми следует сводка:

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

Ключевое отличие от использования time.Sleep заключается в том, что wg.Wait() блокирует выполнение, пока все goroutines не сообщат о завершении с помощью Done(), обеспечивая точную синхронизацию независимо от того, сколько времени занимает каждый download.

Попробуйте сами

package main

import (
	"fmt"
)

func main() {
	// Прочитать количество загрузок
	var n int
	fmt.Scanln(&n)

	// Прочитать информацию о загрузках
	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: Вызвать StartDownloads со срезом downloads
	// TODO: Print the summary: "All downloads complete: [total]KB total"
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Go