Tạo 10.000 goroutine cùng lúc là chạy được về mặt kỹ thuật nhưng sẽ mở 10.000 kết nối, làm đối tác trả 429 hoặc chính service bị hết file descriptor. Cần chặn trần số việc chạy đồng thời.
Cách 1 — semaphore bằng buffered channel. Buffer chính là số slot; lấy token trước khi chạy, trả lại khi xong:
go
sem := make(chan struct{}, 20)
var wg sync.WaitGroup
for _, u := range urls {
wg.Add(1)
sem <- struct{}{} // blocks when 20 are in flight
go func(u string) {
defer wg.Done()
defer func() { <-sem }()
fetch(u)
}(u)
}
wg.Wait()Cách 2 — errgroup với SetLimit. Gọn hơn và gom được lỗi:
go
g, ctx := errgroup.WithContext(ctx)
g.SetLimit(20)
for _, u := range urls {
g.Go(func() error { return fetch(ctx, u) })
}
if err := g.Wait(); err != nil { // first non-nil error; ctx is cancelled
return err
}errgroup.WithContext hủy ctx ngay khi có lỗi đầu tiên, nên các request còn lại tự dừng thay vì chạy nốt vô ích.
Cách 3 — worker pool cố định: chạy sẵn 20 goroutine cùng đọc từ một job channel. Phù hợp khi việc chạy dài và muốn tái dùng tài nguyên đã cấp (connection, buffer) thay vì tạo mới mỗi lần.