select и sync
select мультиплексирует каналы, а sync.WaitGroup ждёт завершения группы горутин.
select — это switch для каналов: он ждёт, пока готова любая из операций, и выполняет соответствующую ветку.
select: ждать несколько каналов сразу
Когда горутина слушает несколько каналов, select позволяет реагировать на тот, который сработает первым. Если готовы сразу несколько — выбирается случайный.
package main
import (
"fmt"
"time"
)
func main() {
fast := make(chan string)
slow := make(chan string)
go func() { time.Sleep(10 * time.Millisecond); fast <- "быстрый" }()
go func() { time.Sleep(50 * time.Millisecond); slow <- "медленный" }()
select {
case msg := <-fast:
fmt.Println("пришло:", msg)
case msg := <-slow:
fmt.Println("пришло:", msg)
}
}Вывод:
пришло: быстрый
С select часто используют тайм-аут: добавляют ветку с case <-time.After(d), чтобы не ждать вечно. Это стандартный приём для защиты от зависших операций.
sync.WaitGroup: дождаться всех
Помните костыль с time.Sleep из прошлого урока? Правильный инструмент — sync.WaitGroup. Это счётчик: Add увеличивает его на число горутин, каждая горутина по завершении вызывает Done, а Wait блокирует, пока счётчик не обнулится.
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done() // отметить завершение
fmt.Printf("воркер %d завершён\n", id)
}(i)
}
wg.Wait() // ждём все три горутины
fmt.Println("все воркеры закончили")
}Вывод (порядок воркеров может отличаться):
воркер 3 завершён воркер 1 завершён воркер 2 завершён все воркеры закончили
Обратите внимание на два момента. Первый: defer wg.Done() гарантирует уменьшение счётчика, даже если горутина упадёт. Второй: i передаётся в горутину аргументом (i) — это защищает от классической ловушки захвата переменной цикла.
sync.Mutex: когда без общей памяти не обойтись
Иногда несколько горутин всё же должны менять общую переменную. Чтобы избежать гонки, её защищают мьютексом: mu.Lock() перед изменением и mu.Unlock() после (обычно через defer). Но в Go это запасной вариант — сперва думают про каналы.
| Инструмент | Зачем |
select | ждать несколько каналов, тайм-ауты |
sync.WaitGroup | дождаться завершения группы горутин |
sync.Mutex | защитить общую память от гонок |
Итог
select— switch для каналов; сtime.Afterдаёт тайм-ауты.WaitGroupчерезAdd/Done/Waitзаменяет костыльtime.Sleep.Mutexзащищает общую память, но в Go это запасной вариант после каналов.