Когда значение изменяемого состояния освобождается от кучи?
-
03-07-2019 - |
Вопрос
В F # WikiBook в разделе Инкапсулирующее Изменяемое состояние раздел, там есть следующий фрагмент кода.
> let incr =
let counter = ref 0
fun () ->
counter := !counter + 1
!counter;;
val incr : (unit -> int)
> incr();;
val it : int = 1
> incr();;
val it : int = 2
> incr();;
val it : int = 3
Поначалу казалось достаточно легким смириться с тем фактом, что изменчивый counter
значение увеличивается каждый раз incr
вызывается.
Но, поразмыслив об этом некоторое время, чего я не мог понять, так это того, когда counter
освобождается от кучи, а также как counter
все еще ссылается на предыдущее значение перед увеличением.Как это counter
, который живет внутри incr
область действия функции сохраняется при нескольких вызовах функций?
Итак, основными вопросами являются:
- Когда происходит
counter
освободился от кучи? - Это не так
counter
утечка памяти?
Решение
Различие между "лексической областью действия" (где имя имеет значение в тексте программы) и "временем жизни" (продолжительность времени выполнения между созданием объекта и уничтожением) иногда может сбивать с толку, поскольку часто эти два понятия сильно коррелируют.Однако техника, продемонстрированная в этом примере, распространена в функциональных языках:вы предоставляете деталям реализации небольшую лексическую область видимости (которая скрывает детали реализации от вызывающих), но продлеваете ее срок службы, фиксируя ее в замыкании (так что ее время жизни становится временем жизни заключающего объекта - в данном случае функции 'incr').Это распространенный способ выполнения инкапсуляции в функциональном программировании (в отличие от обычной техники инкапсуляции public / private в классах в объектно-ориентированном программировании).
Теперь, в этом конкретном примере, похоже, что 'incr' - это функция верхнего уровня, что означает, что ее значение сохраняется в течение всего срока службы программы (или интерактивного сеанса, если вводить в fsi.exe).Вы могли бы назвать это "утечкой", но это зависит от намерения.Если у вас есть какой-то счетчик уникальных идентификаторов, который вам нужен на весь срок службы всей вашей программы, то вам придется хранить этот счетчик в переменной где - то что это длится в течение всей программы.Таким образом, либо это "утечка", либо "из-за конструктивной особенности", в зависимости от того, как будет использоваться "incr" (нужно ли будет использовать эту функцию для всей остальной части программы?).В любом случае, ключевым моментом здесь является то, что 'incr' содержит ресурсы памяти, поэтому, если вам не понадобятся эти ресурсы навсегда, вы должны сделать так, чтобы закрытие, на которое ссылается 'incr', стало недоступным, когда оно больше не понадобится.Обычно это может быть сделано путем придания ему локального значения для какой-либо другой функции, например
let MyComplicatedFuncThatNeedsALocalCounter args =
let incr =
// as before
// other code that uses incr
// return some result that does not capture incr
Другие советы
В данном случае, incr
является функцией верхнего уровня (реализована как статическое поле, если я не ошибаюсь.) Она содержит замыкание, которое, в свою очередь, имеет ссылку на эту ячейку ref с именем counter
.Пока это замыкание существует, ref
ячейка хранится в памяти.
Теперь эта привязка верхнего уровня действительно никогда не будет собирать мусор, поскольку это статическое поле, доступное только для чтения.(в терминах C #).Если у вас, однако, есть подобные замыкания с ограниченный время жизни (привязанное локально или в объекте), ref
ячейка будет освобождена, когда после закрытия будет собран мусор.
счетчик освобождается из кучи, когда incr больше не доступен.Это не утечка памяти из-за сборки мусора.