basura - ¿Cómo detecta Garbage Collector de Python las referencias circulares?
garbage collector python (3)
¿Cómo detecta y libera Python las referencias de memoria circular antes de utilizar el módulo gc?
El recolector de elementos no utilizados de Python (no el módulo de gc
, que es solo la interfaz de Python para el recolector de basura) hace esto. Por lo tanto, Python no detecta y libera referencias de memoria circular antes de utilizar el recolector de elementos no utilizados.
Python generalmente libera la mayoría de los objetos tan pronto como su recuento de referencias llega a cero. (Digo "la mayoría" porque nunca se libera, por ejemplo, enteros pequeños o cadenas internas). En el caso de las referencias circulares, esto nunca ocurre, por lo que el recolector de basura recorre periódicamente la memoria y libera objetos referenciados circularmente.
Esto es todo específico de CPython, por supuesto. Otras implementaciones de Python tienen administración de memoria diferente (Jython = Java VM, IronPython = Microsoft .NET CLR).
Estoy tratando de entender cómo el recolector de basura de Python detecta referencias circulares. Cuando miro la documentación, todo lo que veo es una declaración de que se detectan referencias circulares, excepto cuando los objetos involucrados tienen un método __del__
.
Si esto sucede, mi comprensión (posiblemente defectuosa) es que el módulo gc actúa como una prueba de fallas (supongo) recorriendo toda la memoria asignada y liberando todos los bloques inalcanzables.
¿Cómo detecta y libera Python las referencias de memoria circular antes de utilizar el módulo gc?
¿Cómo detecta y libera Python las referencias de memoria circular antes de utilizar el módulo gc?
No es así El gc existe solo para detectar y liberar referencias circulares. Las referencias no circulares se manejan a través de refcounting.
Ahora, para ver cómo gc determina el conjunto de objetos referenciados por cualquier objeto dado, eche un vistazo a la función gc_get_references
en Modules/gcmodule.c
. El bit relevante es:
// Where `obj` is the object who''s references we want to find
traverseproc traverse;
if (! PyObject_IS_GC(obj))
continue;
traverse = Py_TYPE(obj)->tp_traverse;
if (! traverse)
continue;
if (traverse(obj, (visitproc)referentsvisit, result)) {
Py_DECREF(result);
return NULL;
}
La función principal aquí es tp_traverse
. Cada tipo de nivel C define una función tp_traverse
(o en el caso de objetos que no tienen ninguna referencia, como str
, lo establece en NULL
). Un ejemplo de tp_traverse
es list_traverse
, la función de recorrido para la list
:
static int
list_traverse(PyListObject *o, visitproc visit, void *arg)
{
Py_ssize_t i;
for (i = Py_SIZE(o); --i >= 0; )
Py_VISIT(o->ob_item[i]);
return 0;
}
Veo que hay una declaración de que se detectan referencias circulares, excepto cuando los objetos involucrados tienen un
__del__()
.
Estás en lo cierto: el detector de ciclos de Python puede detectar y recolectar ciclos a menos que contengan objetos con el método __del__
, ya que el intérprete no puede borrar estos objetos de forma segura (para tener una idea de por qué esto es así, imagina que tienes dos objetos con métodos __del__
que se referencian entre sí. ¿En qué orden deberían liberarse?).
Cuando los objetos con un método __del__
están involucrados en un ciclo, el recolector de basura los pegará en una lista separada (accesible a través de gc.garbage
) para que el programador pueda "tratarlos" manualmente.
Creo que encontré la respuesta que estoy buscando en algunos enlaces proporcionados por @SvenMarnich en comentarios a la pregunta original:
Los objetos contenedores son objetos de Python que pueden contener referencias a otros objetos de Python. Listas, Clases, Tuplas, etc. son objetos contenedores; Los enteros, cuerdas, etc. no lo son. Por lo tanto, solo los objetos contenedores están en riesgo de estar en una referencia circular.
Cada objeto de Python tiene un campo - * gc_ref *, que está (creo) establecido en NULL para los objetos que no son contenedores. Para los objetos contenedor, se establece igual al número de objetos no contenedor que hacen referencia a él
Cualquier objeto contenedor con un * gc_ref * contar mayor que 1 (? Hubiera pensado 0, pero ¿está bien por ahora?) Tiene referencias que no son objetos contenedor. Por lo tanto, son accesibles y se eliminan de la consideración de ser islas de memoria inalcanzables.
No es necesario liberar ningún objeto contenedor al que pueda acceder un objeto conocido (es decir, aquellos que acabamos de reconocer que tienen un * gc_ref * mayor que 1).
El resto de los objetos del contenedor no son alcanzables (excepto entre sí) y deben liberarse.
arctrix.com/nas/python/gc es un enlace que proporciona una explicación más completa http://hg.python.org/cpython/file/2059910e7d76/Modules/gcmodule.c es un enlace a la fuente código, que tiene comentarios que explican más los pensamientos detrás de la detección de referencia circular