defer opóźnia wykonanie funkcji do momentu wyjścia z otaczającej funkcji — nawet jeśli wyjście następuje z powodu paniki lub return. Kiedy defer jest używane wewnątrz pętli, wszystkie opóźnione wywołania kumulują się w stosie wywołań defer i są wykonywane w odwrotnej kolejności po zakończeniu otaczającej funkcji.
Może to prowadzić do nieoczekiwanego zużycia zasobów i opóźnień, ponieważ wszystkie opóźnione funkcje zostaną wywołane jednocześnie dopiero po wyjściu z ciała funkcji, a nie po każdej iteracji pętli.
Przykład kodu:
func readFiles(files []string) { for _, name := range files { f, _ := os.Open(name) defer f.Close() // zasoby są zwalniane dopiero po zakończeniu całej funkcji // przetwarzanie pliku ... } }
W tym przykładzie pliki będą pozostawały otwarte do końca wykonania funkcji, co może prowadzić do wycieku deskryptorów przy dużej liczbie plików.
Co się stanie, jeśli użyjesz defer do zamykania zasobów wewnątrz pętli? Dlaczego to nie zawsze jest optymalne?
Odpowiedź: Wszystkie wywołania defer kumulują się i zadziałają dopiero po zakończeniu funkcji, a nie po każdej iteracji. To spowoduje, że zasoby (na przykład otwarte pliki) będą zwalniane zbyt późno.
Poprawnie:
for _, name := range files { f, _ := os.Open(name) // defer f.Close() // nie można! // Poprawnie: process(f) f.Close() }
Historia
W projekcie ładowania logów wystąpił problem: usługa nagle przestała otwierać nowe pliki, mimo że plików było mało. Powód — defer w pętli. Wszystkie pliki były otwierane, a ich zamknięcie było odkładane do końca funkcji. Po przepisaniu na jawne Close() po przetwarzaniu problem zniknął.
Historia
W serwisie zbierającym metryki dla dużych list, używano defer do zrzucania połączenia z bazą wewnątrz pętli przetwarzania danych. Wraz ze wzrostem liczby iteracji pojawiły się opóźnienia i przekroczenie progu otwartych połączeń, co spowodowało awarie serwisu z błędami "too many open connections".
Historia
Inżynier, licząc na "eleganckie" sprzątanie, zastosował defer w pętli czytania dużej liczby plików, co doprowadziło do przekroczenia limitu otwartych deskryptorów na serwerze w produkcji i zatrzymania usługi.