W Go stosuje się pragmatyczne podejście do zarządzania zasobami. Zamiast try-finally, znanego z innych języków, mamy defer: wbudowany mechanizm, który rejestruje "odłożoną" funkcję i wykonuje ją przy wychodzeniu z zakresu widoczności. Ten instrument jest często wykorzystywany do automatycznego zwalniania zasobów (plików, połączeń sieciowych).
Jeśli zapomni się o wywołaniu Close na pliku lub połączeniu, może wystąpić wyciek zasobów lub blokada - co jest krytycznie ważne w aplikacjach serwerowych i plikowych. Odłożone wywołanie z defer zapewnia wywołanie funkcji zakończenia, nawet w przypadku wystąpienia błędu lub panic. Są jednak szczególne przypadki, gdy niewłaściwe użycie defer prowadzi do błędów: wywołanie defer w pętli, przekazywanie niepoprawnego obiektu lub praca z dużą liczbą deferów może prowadzić do overheadu.
Zawsze wywołuj defer f.Close() od razu po pomyślnym otwarciu zasobu, aby uniknąć zapomnianych zamknięć. Nie używaj defer w ciasnych pętlach w celu przyspieszenia i oszczędności pamięci, jeśli otwieranych jest bardzo dużo plików. Staraj się owinąć otwieranie plików/zasobów w funkcję, aby zminimalizować zakres widoczności.
Przykład kodu:
file, err := os.Open("data.txt") if err != nil { log.Fatal(err) } defer file.Close() // zagwarantowane zamknięcie // ... praca z plikiem
Kluczowe cechy:
Kiedy wykonuje się defer i obliczane są jego parametry?
Parametry funkcji w defer obliczane są natychmiast w momencie deklaracji defer, a nie przy wykonaniu defer.
Przykład kodu:
func main() { a := 1 defer fmt.Println(a) // zapamięta 1 a = 42 } // wyświetli 1
Czy defer może prowadzić do wycieków pamięci lub spowolnień?
Tak, jeśli używasz defer w pętli, w której otwieranych jest wiele plików lub obiektów, każdy taki defer jest zapisywany na stosie odłożonych wywołań, który jest oczyszczany tylko przy wychodzeniu z funkcji, co prowadzi do niepotrzebnego wzrostu pamięci.
Przykład kodu:
for i := 0; i < 10000; i++ { f, _ := os.Open("file.txt") defer f.Close() // trzyma wszystkie 10000 plików otwartych do końca main }
Co się stanie, jeśli wywołanie f.Close() zwraca błąd, a on jest "łykany"?
Standardowa praktyka - logowanie błędu zamknięcia zasobów. Jeśli ten moment jest ignorowany, można nie zauważyć awarii lub częściowego zapisu plików, na przykład przy niedałności usunięcia pliku tymczasowego lub przy awariach sieci.
W pętli przetwarzania plików programista ustawia defer f.Close() dla każdego pliku. W rezultacie otwartych jest jednocześnie dziesiątki tysięcy plików, co spowalnia wykonanie programu, a w systemie kończą się deskryptory plików.
Zalety:
Wady:
W pętli przetwarzanie każdego pliku odbywa się w osobnej funkcji, w której defer f.Close() jest tylko jeden na przetwarzanie i natychmiast zwalnia zasób.
Zalety:
Wady: