Perl - перехресні посилання та - витік пам’яті - Perlover - s Blog
Нещодавно зіткнувся з такою не очевидною проблемою, як «відплив пам'яті» там, де начебто її не мало бути.
Отже, розглянемо кілька прикладів, коли може бути витік пам'яті, який не так просто помітити:
Побачимо, що тут. Отже,$a містить посилання на хеш-таблицю,$b - на іншу. Усі вони — обернуті до блоку. Здавалося б,$a &$b повинні звільнити пам'ять після закінчення блоку, оскільки після блоку видимість змінних зникає і у разі perl все очищає. Тим більше, посилання на хеш-таблиці ми нікуди не передавали і не повертали через return . Але все буде негаразд.$a-> отримує посилання$b. Якби цим усе й обмежилося, пам'ять би звільнилася. Але проблема в тому, що $b-> зберігає посилання на$a, яке має посилання на$b. Це і є ключовим моментом!Коли дві змінні, як або пов'язані через посилання один на одного (або прямі посилання, або через хеші, як тут, або через посилання на функції, що їх використовують) -очищення пам'яті не відбувається. І все тому, що perl не звільняє$a тому, що$b має посилання на неї (тут лічильник посилань для хешу під $a дорівнює 2 всередині блоку, для хешу під $b також дорівнює 2 всередині блоку), а $a має посилання $b. Коли блок закінчується, perl зменшує лічильники посилань в обох, і якщо в звичайній ситуації вся свелість до того, що в результаті у всіх настало б по «нулях», то тут у обох лічильники посилання так і залишаться рівними одиницями. Вихід - можна всередині блоку наприкінці написати def $b-> (як би розрубавши гордієв вузол), тим самим зруйнуємо перехресність посилань.
Але є приклад і цікавіше, що трапився зі мною. Вінприблизно такий:
Тут знову ж таки перехресні посилання, але більш витончено.$ref має у своєму складі посилання на функцію, наприклад — callback за своєю суттю, а сам callback у собі використовує$ref змінну. Виходить, що коли справа дійде до чищення пам'яті — буде така ж ситуація — перехресні посилання і пам'ять не буде звільнена під $ref хеш, так і пам'ять змінних стеків під sub<> (функції можуть «утримувати» дані, які їм доступні з їхнього «контексту» видимості та які вони використовують). Як бути?
Другий приклад вирішується одним способом, як не дивно (може їсти й інший, з тим самим undef, але цей найефективніший і «правильніший»). Є такий сервісний модуль - Scalar::Util з функцією weaken. Код вище змінюється на такий:
Функція weaken управляє саме тим лічильником посилань. Якщо говорити точно, вона перестає «утримувати» лічильник посилань той об'єкт, який посилається. А саме, наприклад,$ref — утримує без weaken лічильник на хеш — <>. Після$ref = <> лічильник посилань хешу дорівнює двом — одиниці він дорівнював відразу після створення хешу, а потім ще збільшився на ялинку після присвоєння $ref-у. Якщо ми використовуємоweaken $ref, тоді після цього лічильник знову зменшиться на одиницю для хеша, а всередині perl даних десь у глибині це запам'ятається, і тоді будь-якої миті, як тільки perl очистить пам'ять під хеш ( тобто коли лічильник посилань стане нульовим), змінна$ref стане рівноюundef. Але, як правило, якщо правильно написати програму, виникнення undef значення для $ref нам не завадить. Тут головне, запам'ятати просте правило - якщо ви маєте посилання на функцію, яка сама як або використовує ту змінну, яка посилається на неї (прямо або опосередковано) - тут требазастосовувати weaken, інакше буде витік пам'яті! І це буде не вина perl, а ваша! Я у своїй великій практиці програмування на perl поки що не стикався з витоком пам'яті з вини perl - все було з вини програміста, тобто мене 😉