¿Cuándo se libera un valor de estado mutable del montón?
-
03-07-2019 - |
Pregunta
En F # WikiBook en la sección Encapsulando el estado mutable , hay un fragmento de código siguiente .
> 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
Al principio, parecía bastante fácil tragarse el hecho de que el valor mutable del counter
aumenta cada vez que se invoca incr
.
Pero después de pensarlo por un tiempo, lo que no pude entender fue cuándo counter
se libera del montón y también cómo counter
todavía se refiere al valor anterior antes de ser incrementado . ¿Cómo es que el contador
que vive dentro del alcance de la función incr
sobrevive a través de múltiples llamadas de función?
Entonces las preguntas principales son:
- ¿Cuándo se libera
counter
del montón? - ¿No es
counter
una pérdida de memoria?
Solución
La distinción entre 'alcance léxico' (donde un nombre tiene significado en el texto de un programa) y 'duración' (duración del tiempo de ejecución entre el momento en que se crea y destruye el objeto) a veces puede ser confusa, ya que a menudo estos dos están altamente correlacionados . Sin embargo, la técnica demostrada en este ejemplo es común en lenguajes funcionales: le da a un detalle de implementación un pequeño alcance léxico (que oculta los detalles de implementación de las personas que llaman), pero extiende su tiempo de vida al capturarlo en un cierre (para que su tiempo de vida se convierta en el tiempo de vida del objeto envolvente, en este caso la función 'incr'). Esta es una forma común de encapsular en programación funcional (en contraste con la técnica de encapsulación habitual de público / privado en clases en programación orientada a objetos).
Ahora, en este ejemplo en particular, parece que 'incr' es una función de nivel superior, lo que significa que su valor dura toda la vida del programa (o sesión interactiva si se escribe en fsi.exe). Podría llamar a esto una "fuga", pero depende de la intención. Si tiene un contador de identificación único que necesita durante toda la vida de todo su programa, entonces tendrá que almacenar ese contador valioso en algún lugar que dura todo el programa. Entonces, esto es 'una fuga' o 'una característica de diseño' dependiendo de cómo se usará 'incr' (¿necesitará usar esa función para todo el resto del programa?). En cualquier caso, el punto clave aquí es que 'incr' contiene recursos de memoria, por lo que si no necesita esos recursos para siempre, debe hacer arreglos para que el cierre al que hace referencia 'incr' se vuelva inalcanzable cuando ya no sea necesario. Comúnmente, esto podría ser haciendo que sea local para alguna otra función, por ejemplo
let MyComplicatedFuncThatNeedsALocalCounter args =
let incr =
// as before
// other code that uses incr
// return some result that does not capture incr
Otros consejos
En este caso, incr
es una función de nivel superior (implementada como un campo estático si no me equivoco). Tiene un cierre que a su vez tiene una referencia a esa celda de referencia llamada counter
. Mientras exista este cierre, la celda ref
se mantiene en la memoria.
Ahora, este enlace de nivel superior nunca obtendrá basura recolectada ya que es un campo de solo lectura estático. (en términos de C #). Sin embargo, si tiene cierres como ese con una vida útil limitada (enlazada localmente o en un objeto), la celda ref
se liberará cuando el cierre se recolecte basura.
se libera del montón cuando ya no se puede acceder a incr. No es una pérdida de memoria debido a la recolección de basura.