Middle+
// Lors de la mise à jour d'une commande, nous devons envoyer les données de la commande à plusieurs services (API tierces) // le nombre de services augmente (peut-être des milliers+) // nous avons écrit le code, au début tout allait bien, mais avec le temps notre service a commencé à consommer beaucoup de mémoire func (s *orderService) SendOrder(ctx context.Context, hosts []string, order Order) { for i := 0; i < len(hosts); i++ { go func() { // Imaginons que c'est un appel réseau long response, err := s.httpClient.Send(ctx, hosts[i], order) if err != nil { s.logger.Error(ctx, "échec de l'envoi", err) return } s.logger.Info(ctx, "succès", response) }() } }
sobes.tech IA
Réponse de l'IA
В представленном коде есть проблема с использованием переменной цикла внутри горутины — переменная hosts[i] может измениться к моменту выполнения горутины, что приведёт к неправильным вызовам.
Кроме того, при большом количестве сервисов (тысячи и более) запуск тысяч горутин одновременно приведёт к высокому потреблению памяти и ресурсов.
Рекомендации по исправлению:
- Передавать переменную в замыкание, чтобы избежать захвата изменяющейся переменной:
for i := 0; i < len(hosts); i++ {
host := hosts[i]
go func() {
response, err := s.httpClient.Send(ctx, host, order)
if err != nil {
s.logger.Error(ctx, "failed to send", err)
return
}
s.logger.Info(ctx, "success", response)
}()
}
- Ограничить количество одновременно работающих горутин с помощью семафора или worker pool, чтобы не создавать тысячи горутин одновременно:
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{}{}
}
Таким образом, мы избегаем утечек памяти и контролируем нагрузку на систему.