Ответ
sync.WaitGroup — это механизм синхронизации, который позволяет основной горутине ожидать завершения выполнения набора других (дочерних) горутин.
Основные методы:
-
Add(delta int)Увеличивает внутренний счетчик активных горутин наdelta. Важно вызывать этот метод в основной горутине до запуска дочерней горутины, чтобы избежать состояния гонки. -
Done()Уменьшает счетчик на единицу. Обычно вызывается в конце работы горутины с помощьюdefer, чтобы гарантировать выполнение даже в случае паники внутри горутины. -
Wait()Блокирует горутину, в которой был вызван, до тех пор, пока внутренний счетчик не станет равен нулю.
Пример использования:
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // Уменьшаем счетчик при выходе из функции
fmt.Printf("Worker %d startingn", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d donen", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1) // Увеличиваем счетчик перед запуском горутины
go worker(i, &wg)
}
fmt.Println("Main: Waiting for workers to finish...")
wg.Wait() // Ожидаем, пока счетчик не станет 0
fmt.Println("Main: All workers finished. Exiting.")
}
Ключевые правила и частые ошибки:
- Вызов
Addдо запуска горутины: Если вызватьwg.Add(1)внутри самой горутины,wg.Wait()может сработать раньше, чемAddвыполнится, что приведет к преждевременному завершению программы. - Отрицательный счетчик: Количество вызовов
Done()не должно превышать количество, указанное вAdd. Это вызовет паникуpanic: sync: negative WaitGroup counter. - Копирование
WaitGroup:WaitGroupнельзя копировать после первого использования. Ее следует передавать по указателю (*sync.WaitGroup). - Переиспользование: После того как
Wait()отработал и счетчик обнулился,WaitGroupможно использовать заново для новой группы горутин, снова вызвавAdd.
Ответ 18+ 🔞
Слушай, а вот этот твой sync.WaitGroup — это ж такая штука, чтобы главная горутина не сбежала раньше времени, пока все её мелкие подчинённые не отработают. Как мамка у подъезда ждёт, пока все дети с прогулки не приползут, блядь.
Короче, три его главных прикола:
Add(delta int)— это типа крикнуть: «Так, я сейчас трёх хулиганов на улицу выпускаю, ждите!». Увеличиваешь счётчик наdelta. Делать это надо до того, как горутину запустил, а то она уже убежала, а ты ещё считаешь — пиздец, недосчитаешься.Done()— это когда один хулиган пришёл домой и дверь за собой закрыл. Уменьшает счётчик на единичку. Обычно его черезdeferставят, чтобы даже если горутина обосрётся с паникой, она всё равно «отметилась», что пришла. Ответственность, блядь.Wait()— это собственно мамка, которая стоит и курит, пока счётчик не станет ноль. Блокируется, короче, до победного.
Вот смотри, как это в деле выглядит, ёпта:
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // Что бы ни случилось — я вернулся, чёрт возьми!
fmt.Printf("Рабочий %d начал страдатьn", id)
time.Sleep(time.Second) // Делаем вид, что пашем
fmt.Printf("Рабочий %d закончил страдатьn", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1) // Внимание, ещё один страдалец на выход!
go worker(i, &wg)
}
fmt.Println("Главный: Сижу, жду, когда все настрадаются...")
wg.Wait() // Сижу тут, блядь, пока все трое не отчитаются
fmt.Println("Главный: Всё, все выдохлись. Можно идти.")
}
А теперь главное — где все обычно ебутся:
Addв неположенном месте. Еслиwg.Add(1)запихнуть внутрь горутины, то мамка (Wait()) может уже «всё, я пошла» сказать, пока ты там в другой комнате только собираешься счётчик увеличить. Раньше времени разблокируется — и пиши пропало.- Перебор с
Done. ВызвалDone()лишний раз — получиpanic: sync: negative WaitGroup counter. Это как если бы ты сказал, что домой вернулось пятеро, а выпускал-то ты только троих. Пиздец, откуда двое взялись? Математика, сука! - Попытка скопировать.
WaitGroup— как зубная щётка, индивидуальная. После того как начал её использовать — не копируй, а передавай дальше указателем (&wg), а то сломаешь. - Можно использовать повторно. Всё обнулилось после
Wait()? Ну и хуй с ним, можно сноваAddвызывать и новую партию рабов запускать. Многоразовая штука.
Вот и вся магия, нехуй тут сложного. Главное — считай правильно, а то эти горутины, они как тараканы: выпустил, а обратно не всех собрал, потом по всей программе ползают.