programowanieBackend Developer

Jak działa mechanizm defer w pętlach i funkcjach lambda w Go? Jakie mogą być niebezpieczeństwa związane z używaniem defer wewnątrz pętli?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

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.

Pytanie z pułapką

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() }

Przykłady rzeczywistych błędów z powodu braku znajomości tematów


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.