¿Cómo trata un recolector de basura concurrente con variables?

8

Digamos que es un recolector de basura de marcado y barrido concurrente.

Cuando dicho GC maneja punteros constantes, simplemente los recorre (comenzando desde las raíces) y marca cada bloque de datos encontrado. Luego barre todo sin marcar. Un código de cliente debe marcar los bloques de datos que usa como raíces.

¿Pero qué hacer con las variables? Aquí hay una situación:

  1. Ves una variable que almacena un puntero al objeto A.
  2. Thread 1lee Vy suspende.
  3. Thread 2modifica Vy hace que apunte al objeto B.
  4. El recolector de basura ejecuta su fase de "marca" y encuentra que Aya no se hace referencia, luego lo desasigna durante la fase de "barrido".
  5. Thread 1despierta e intenta usar A(ya leyó Ven el paso 2) marcándolo como root. Y falla , porque Aya no existe.

Entonces, ¿cómo manejar esto?

El Thread 2puede marcar el objeto reemplazado Acon una bandera especial do-no-remove (bandera similar se utiliza para objetos recién asignados). ¿Pero cuándo se debe eliminar esta bandera? Por supuesto que Thread 1podría hacer eso. Pero Thread 2no sabe nada Thread 1y, por lo tanto, no puedo estar seguro de que esto se haga nunca. Esto puede llevar a Aque nunca se libere. Y si GC eliminará esa bandera, entonces nada impide que Ase elimine cuando GC se ejecute por segunda vez ...

Las descripciones del recolector de basura de marca y barrido sobre la marcha que he leído solo mencionan que el objeto reemplazado debe estar "atenuado". Pero sin ningún detalle. Un enlace a una descripción más detallada de la solución sería muy apreciado.

lorus
fuente

Respuestas:

4

Dependiendo de los detalles precisos de la implementación del recolector de basura, esto puede no ser un problema en absoluto en su paso 4. Por ejemplo, en el paso 2, el hilo 1 presumiblemente se lee Ven un registro. El recolector de basura probablemente necesitará examinar el contenido de los registros para todos los subprocesos activos (en ejecución y suspendidos) para ver si hay una referencia a algún objeto contenido en los registros.

Inevitablemente, la implementación de un recolector de basura está estrechamente vinculada al entorno operativo (y de subprocesamiento) en el que se ejecuta. Existen muchas técnicas de implementación para garantizar que se tengan en cuenta todas las referencias almacenadas y transitorias.

Greg Hewgill
fuente
Pero, ¿hay alguna forma de hacerlo de forma independiente de la plataforma? El registro puede contener los datos, que solo parecen un puntero, pero en realidad no lo son. Mi implementación de GC es exacta y se basa en el hecho de que cualquier puntero que procesa apunta a un bloque de datos de cierta estructura.
lorus
Hmm, pero esta es una idea! Puedo colocar dicho puntero en algún lugar conocido en la pila (o variable local de hilo) y hacer que GC lo examine.
lorus
@lorus: para muchos recolectores de basura, las tablas generadas por el compilador le dicen al GC qué registros contienen punteros en cualquier punto dado de un método. Para otros, el GC solo se ejecuta en un "punto seguro" que invalida implícitamente el contenido del registro. Además, las implementaciones de GC dependen inherentemente de la plataforma en algún nivel.
Stephen C
@StephenC Bueno, no creo que un algoritmo de propósito general como la recolección de basura requiera ser tan dependiente de la plataforma. Operaciones atómicas y barreras de memoria: sí. ¿Pero acceso directo a los registros? No, no lo creo. Sería eficiente, pero creo que no es absolutamente necesario. Me gustaría saber más sobre estos algoritmos independientes de la plataforma.
lorus
0

Debe marcar las variables locales en algún momento durante la fase de marca. Todas las variables locales, incluidas las que normalmente viven en la pila. De algun modo.

Además, creo que debe hacerse durante la fase síncrona (todos los mutantes detenidos) de escanear objetos modificados. De hecho, el mismo problema podría surgir incluso sin considerar las variables / registros locales. Considere el objeto A apuntando a nulo y el objeto B apuntando a C. Ahora escanea el objeto A, aparece un hilo mutador, copia la referencia a C de B a A, anula B. Y ahora puede escanear B. Y C se deslizó debajo de tus dedos

No conozco ninguna forma de lidiar con esto que no implique detener a los mutadores. La técnica habitual es al final de la fase de marcado para detener a todos los mutadores y volver a marcar todos los objetos que mutaron durante la fase de marcado principal. E incluya pilas y registros en eso.

Los registros de marcado normalmente se solucionan haciéndolo sincrónicamente llamando al recopilador en el hilo a veces. Dentro de la función de recopilador, solo sus propias variables locales (que no son raíces) pueden estar en registros, todas las demás variables locales en la cadena de llamadas están en la pila, por lo que puede caminar por la pila.

Alternativamente, puede enviar una señal al hilo. El controlador de señal volverá a forzar todas las variables en la pila, para que pueda recorrerlas. La desventaja de este método es que depende de la plataforma.

Jan Hudec
fuente
Si. Pero la situación de la que hablaba no se trata de una variable local. Se trata de uno global, al que se puede acceder y modificar simultáneamente por diferentes hilos.
lorus
si después de Thread1 tiene V lectura y A no está en una variable local del Thread1 entonces A no ser alcanzable después thread2 V modificado, por lo que estará listo para la recolección de todos modos
monstruo de trinquete
@lorus: Si un hilo carga algo en el registro, el registro es su variable local. No importa si fue mencionado en una fuente de alto nivel o agregado por el compilador. Simplemente lo trata como una variable local.
Jan Hudec
@ JanHudec Ok. Ahora lo entiendo. Entonces, ¿llamar al recopilador en el hilo (cliente) es en realidad una especie de bloqueo global?
lorus
1
@JanHudec, a menos que los subprocesos funcionen con el GC y puedan agregar objetos a una cola para marcar, cada vez que un subproceso lee una referencia
Ratchet Freak