G
go-with-me
@golangwithus1.9K подп.
2.9Kпросмотров
1 августа 2025 г.
📷 ФотоScore: 3.2K
🤪 WaitGroup Pitfalls Сегодня речь пойдет про крайние случаи при работе с WaitGroup, о которой мы уже говорили в предыдущем посте. Советую обратиться к нему с целью изучения внутренней структуры этой сущности и понимания того, что такое counter Предлагаю незамедлительно приступить к делу! 1. Паника при негативном счетчике Запомните ключевое - Done под капотом является вызовом Add со значением -1 (тык). Основываясь на внутрянке метода Done и внутренней имплементации метода Add, в котором заложена паника после инкрементации аргумента delta (тык & тык), можно вывести следующие правила: — При прямом вызове Add с отрицательным аргументом delta необходимо быть уверенными в том, что мы не уйдем в отрицательный counter — Количество вызовов Done должно быть равно сумме аргументов всех Add func A() { var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() // panic: sync: negative WaitGroup counter wg.Done() }() wg.Wait() } 2. Паника при Add после Wait В случае неполного выхода из ожидания на Wait и мгновенного вызова Add происходит зашитая в метод Wait паника (тык) По своей сути WaitGroup является переиспользуемой сущностью, то есть мы в праве запустить новый пул потоков для ожидания, если к этому моменту будем иметь обнуленный счетчик counter и полный выход из метода Wait Не следуя этому правилу, мы получим панику, что и происходит при запуске данного сниппета: func B() { const N = 10_000 var wg sync.WaitGroup for range N { wg.Add(2) go wg.Done() go wg.Done() go func() { wg.Wait() // panic: WaitGroup is reused before previous Wait has returned }() } } 3. Каждый Add сопровождается Done Этот кейс предельно тривиален. В случае, когда сумма всех аргументов не соотносится количеству вызовов Done мы получим вечное ожидание потока, где прожат Wait, следовательно мы должны строго следить за тем, чтобы после каждого Add происходило соответствующее количество вызовов Done func C() { const N = 1_000 var wg sync.WaitGroup wg.Add(N) for i := 0; i < N; i++ { if i%2 == 0 { continue } wg.Add(1) go func(i int) { defer wg.Done() fmt.Println(i) }(i) } wg.Wait() } 4. Мультиплексирование Wait на несколько потоков Мало кто знает, но на одном экземпляре WaitGroup можно вызывать Wait в разных потоках, а это значит, что мы можем строить отношения вида N:M, где N - потоки, ответственные за вызов wg.Done(), а M - ожидающие потоки func F() { const ( N = 10 M = 5 ) var wgWork, wgWait sync.WaitGroup for i := 0; i < N; i++ { wgWork.Add(1) go worker(&wgWork, i) } for i := 0; i < M; i++ { wgWait.Add(1) go waiter(&wgWork, &wgWait, i) } wgWait.Wait() } func worker(wg sync.WaitGroup, idx int) { defer wg.Done() fmt.Printf("[%d] worker start&#092;n", idx) time.Sleep(5 time.Second) fmt.Printf("[%d] worker end&#092;n", idx) } func waiter(wgWorkers, wgWaiters *sync.WaitGroup, idx int) { defer wgWaiters.Done() fmt.Printf("[%d] waiter start&#092;n", idx) wgWorkers.Wait() fmt.Printf("[%d] waiter end&#092;n", idx) } Статью писали с Дашей: @dariasroom Stay tuned 🐈 #golang #concurrency
2.9K
просмотров
3303
символов
Да
эмодзи
Да
медиа

Другие посты @golangwithus

Все посты канала →
🤪 WaitGroup Pitfalls Сегодня речь пойдет про крайние случаи — @golangwithus | PostSniper