Конкурентность в Go¶
TL;DR¶
go f()— запустить горутину (~2 KB stack, миллионы возможны).- Канал — типизированная очередь между горутинами.
- Unbuffered: send блокируется до receive.
sync.WaitGroup— ждать N горутин.sync.Mutex/RWMutex— критическая секция.context.Context— отмена и таймауты.go run -race— детектор гонок, всегда включай в CI.
Горутины¶
Стартуют через go, шедулятся runtime'ом Go (модель G/M/P). Стек растёт
динамически, начинается с ~2 KB.
Каналы¶
ch := make(chan int) // unbuffered
ch := make(chan int, 100) // buffered, capacity 100
ch <- 1 // send
v := <-ch // receive
v, ok := <-ch // ok=false если канал закрыт И буфер пуст
close(ch) // закрыть
for v := range ch { ... } // итерация до close
Правила: 1. Закрывает только sender. 2. Send в closed → panic. 3. Receive из closed → возвращает zero value моментально. 4. nil-канал в select никогда не срабатывает (полезно для динамического отключения case).
sync.WaitGroup¶
Ждём N горутин:
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
// работа
}(i)
}
wg.Wait()
sync.Mutex / RWMutex¶
Защищаем shared state:
RWMutex для read-heavy: много RLock() параллельно, Lock() только
один.
atomic¶
Для одной переменной — atomic дешевле Mutex'а:
context.Context¶
Отмена и таймауты:
ctx, cancel := context.WithTimeout(parentCtx, 5*time.Second)
defer cancel()
select {
case result := <-doWork(ctx):
return result
case <-ctx.Done():
return nil, ctx.Err()
}
Конвенция: первый аргумент функции — ctx context.Context.
Worker pool¶
func worker(jobs <-chan Task, results chan<- Result) {
for j := range jobs {
results <- process(j)
}
}
jobs := make(chan Task, 100)
results := make(chan Result, 100)
for w := 0; w < N; w++ {
go worker(jobs, results)
}
for _, t := range tasks {
jobs <- t
}
close(jobs)
Параллельный fetch с лимитом¶
Fan-out/fan-in с семафором:
sem := make(chan struct{}, 10) // max 10 in-flight
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1)
sem <- struct{}{}
go func(u string) {
defer wg.Done()
defer func() { <-sem }()
fetch(u)
}(url)
}
wg.Wait()
Race condition и detector¶
Включай race detector в CI всегда (стоит ~2x памяти, ~10% CPU).
Deadlock¶
Простое правило: всегда захватывай несколько mutex'ов в одном порядке. Если нужен inverse — переписывай на каналы или actor-pattern.
📖 Связанные задачи: must-solve-100 → C-013..C-018, M-001..M-004.