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.
Задание
ЛегкоДавайте создадим менеджер загрузок, который координирует несколько 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"
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker PoolПотренируйтесь самостоятельно: Онлайн-компилятор Go