// Al actualizar un pedido, debemos enviar los datos del pedido a varios servicios (de terceros) // el número de servicios está creciendo (puede ser miles+) // escribimos el código, al principio todo iba bien, pero con el tiempo nuestro servicio comenzó a consumir muchos recursos func (s *orderService) SendOrder(ctx context.Context, hosts []string, order Order) { for i := 0; i < len(hosts); i++ { go func() { // Imaginemos que esto es una llamada de red larga response, err := s.httpClient.Send(ctx, hosts[i], order) if err != nil { s.logger.Error(ctx, "falló al enviar", err) return } s.logger.Info(ctx, "éxito", response) }() } }
sobes.tech AI
Respuesta de la IA
В представленном коде есть проблема с использованием переменной цикла i внутри горутины:
for i := 0; i < len(hosts); i++ {
go func() {
response, err := s.httpClient.Send(ctx, hosts[i], order)
// ...
}()
}
Здесь i используется внутри анонимной функции, но к моменту запуска горутины значение i может измениться, что приведёт к ошибкам (например, выход за пределы массива).
Кроме того, при большом количестве сервисов (тысячи и более) запуск тысячи горутин одновременно может привести к чрезмерному потреблению ресурсов (память, CPU, сетевые соединения).
Рекомендации:
- Исправить замыкание переменной
i:
for i := 0; i < len(hosts); i++ {
host := hosts[i] // локальная копия
go func(h string) {
response, err := s.httpClient.Send(ctx, h, order)
// ...
}(host)
}
- Ограничить количество одновременно работающих горутин:
Использовать семафор или пул воркеров, чтобы не создавать слишком много горутин одновременно.
Пример с семафором:
sem := make(chan struct{}, 100) // максимум 100 одновременных горутин
for _, host := range hosts {
sem <- struct{}{} // захват семафора
go func(h string) {
defer func() { <-sem }() // освобождение семафора
response, err := s.httpClient.Send(ctx, h, order)
if err != nil {
s.logger.Error(ctx, "failed to send", err)
return
}
s.logger.Info(ctx, "success", response)
}(host)
}
// дождаться завершения всех горутин
for i := 0; i < cap(sem); i++ {
sem <- struct{}{}
}
- Использовать worker pool:
Создать фиксированное количество воркеров, которые будут читать из канала с хостами и отправлять запросы.
- Обрабатывать ошибки и таймауты:
Для устойчивости важно контролировать таймауты и корректно обрабатывать ошибки.
Таким образом, чтобы избежать роста потребления ресурсов, нужно контролировать параллелизм и правильно работать с переменными в замыканиях.