programowanieStarszy programista Perl, Backend/Fullstack

Wyjaśnij cechy pracy z zamknięciami (closures) i anonimowymi podprogramami w Perl. Jak ich poprawnie używać, gdzie pojawiają się subtelności i jak uniknąć wycieków pamięci?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

W Perl obsługiwane są anonimowe podprogramy i zamknięcia. Anonimowe podprogramy deklarowane są za pomocą sub bez nazwy i zwracają odniesienie do kodu. Zamknięcie to podprogram, który "przechwytuje" leksykalny zakres, w tym zmienne, które istniały w momencie jego utworzenia.

Przykład anonimowego podprogramu i closure:

sub make_incrementer { my $inc = shift; return sub { $_[0] + $inc }; } my $inc10 = make_incrementer(10); print $inc10->(5); # Wyświetli 15

Zamknięcia są aktywnie stosowane do tworzenia fabryk funkcji, generatorów i callbacków (na przykład w map/grep, obsługach zdarzeń).

Ważny punkt: jeśli zamknięcie odnosi się do zmiennych, które z kolei wskazują na samo zamknięcie (bezpośrednio lub przez strukturę), powstaje cykliczne odniesienie i może wystąpić wyciek pamięci.

Pytanie z podstępem

Kiedy zmienne zamknięte w zamknięciu są zwalniane? Czy jest różnica w zachowaniu dla my i our?

Odpowiedź: Zmienne my, zamknięte wewnątrz zamknięcia, pozostają żywe tak długo, jak istnieje przynajmniej jedno odniesienie do samego zamknięcia. Nie są zwalniane po zakończeniu funkcji, ich czas życia to cały czas życia zamknięcia. W przypadku zmiennych our takiego zachowania nie ma — są dostępne dla wszystkich zamknięć i zwalniane po zakończeniu programu. Zapominając usunąć zamknięcie, można uzyskać wycieki pamięci.

Przykład problemu:

my $cref; { my $val = 5; $cref = sub { $val++ }; } # $val wciąż istnieje, dopóki żyje $cref

Przykłady rzeczywistych błędów z powodu niewiedzy o subtelnościach tematu


Historia

W aplikacji serwerowej dla każdego użytkownika tworzono zamknięcie-kontekst dla callback. Ale zamknięcie również wskazywało na strukturę użytkownika, co prowadziło do cyklicznego odniesienia. Z tego powodu garbage collector nie oczyszczał obiektów użytkownika nawet po wylogowaniu — wycieki pamięci rosły wykładniczo.


Historia

W aplikacji-demonie do przetwarzania zdarzeń w tle używano zamknięć z zamkniętymi zmiennymi-wskaźnikami, ale zapomniano wyczyścić tablice, na które one wskazywały. W efekcie, z powodu kilku zapomnianych zamknięć, przypadkowo gromadziły się stare dane wiadomości, aż do awarii demona konieczne było ręczne czyszczenie pamięci.


Historia

Programista próbował użyć zmiennej our w zamknięciu, oczekując "zamkniętego" zachowania — ale wszystkie zamknięcia dzieliły jedną zmienną, co prowadziło do wyścigów danych przy równoległym wykonywaniu. Błąd ujawnił się podczas jednoczesnej pracy użytkowników z różnymi parametrami (błędne obliczenia).