5

Supongamos que tiene un objeto contado de referencia en la memoria compartida. El recuento de referencias representa el número de procesos que utilizan el objeto, y los procesos son responsables de incrementar y disminuir el recuento mediante instrucciones atómicas, por lo que el recuento de referencia también se encuentra en la memoria compartida (podría ser un campo del objeto o el objeto podría contener un puntero al recuento, estoy abierto a sugerencias si ayudan a resolver este problema). Ocasionalmente, un proceso tendrá un error que le impedirá disminuir el recuento. ¿Cómo hace que sea tan fácil como sea posible averiguar qué proceso no está disminuyendo el conteo?¿Cómo se pueden depurar con eficacia los problemas de recuento de referencias en la memoria compartida?

Una solución en la que he pensado es dar a cada proceso un UID (quizás su PID). Luego, cuando los procesos disminuyen, insertan su UID en una lista vinculada almacenada junto con el recuento de referencias (elegí una lista vinculada porque puedes agregar atómicamente a la cabeza con CAS). Cuando desee depurar, tiene un proceso especial que mira las listas enlazadas de los objetos que aún están vivos en la memoria compartida, y los UIDs de las aplicaciones que no están en la lista son los que aún tienen que disminuir el recuento.

La desventaja de esta solución es que tiene un uso de memoria O (N) donde N es el número de procesos. Si la cantidad de procesos que usan el área de memoria compartida es grande y tiene una gran cantidad de objetos, esto se vuelve muy costoso rápidamente. Sospecho que podría haber una solución a mitad de camino donde con información parcial de tamaño fijo podría ayudar a la depuración de alguna manera, pudiendo reducir la lista de posibles procesos, incluso si no se puede identificar uno solo. O bien, si pudieras detectar qué proceso no ha decrementado cuando solo un proceso no lo ha hecho (es decir, si no se puede controlar la detección de 2 o más procesos que no reducen el recuento), eso probablemente sea una gran ayuda.

(Hay más soluciones "humanas" para este problema, como asegurarse de que todas las aplicaciones utilicen la misma biblioteca para acceder a la región de memoria compartida, pero si el área compartida se trata como una interfaz binaria y no todos los procesos van a Las aplicaciones escritas por usted que están fuera de su control. Además, incluso si todas las aplicaciones usan la misma biblioteca, una aplicación puede tener un error fuera de la biblioteca que corrompe la memoria de tal manera que no puede disminuir el conteo. Sí, estoy usando un lenguaje inseguro como C/C++;)

Editar: En situaciones de proceso único, tendrá el control, por lo que puede usar RAII (en C++).

+1

Una vez que descubro cómo depurar eficientemente los problemas de recuento de referencias en la memoria normal (proceso único), pasaré a este problema. –

+0

Este es un problema muy difícil porque DCOM nunca obtuvo una buena solución para –

+0

@Terry: en una sola situación de proceso puede usar RAII. –

Respuesta

7

Puede hacerlo utilizando solo un entero adicional por objeto.

Inicialice el número entero en cero. Cuando un proceso incrementa el recuento de referencia para el objeto, se XORs su PID en el número entero:

object.tracker ^= self.pid; 

Cuando un proceso disminuye el recuento de referencia, se hace lo mismo.

Si el recuento de referencias se deja en 1, el entero del rastreador será igual al PID del proceso que lo incrementó pero no lo disminuyó.


Esto funciona porque XOR es conmutativa ((A^B)^C == A^(B^C)), por lo que si un proceso XORs el rastreador con su propio PID un número par de veces, que es la misma que la operación XOR con PID^PID - que es cero, lo cual deja el valor del rastreador no afectado.

De forma alternativa, puede usar un valor sin signo (que se define para envolver en lugar de desbordar), agregando el PID al incrementar el recuento de uso y restarlo al disminuirlo.

+0

Ja, tuve el mismo pensamiento y luego me convencí de que estaba equivocado con un contraejemplo de matemáticas mental incorrecto.Si actualiza para explicar por qué esto funciona (xor es obviamente conmutativo y X^X se deshace) lo marcaré como aceptado :) –

+0

Normalmente soy escéptico con los hacks de manipulación de bits, pero este es ingenioso. ¡Bonito! – Kena

+0

¿No será erróneo sumar y restar? Digamos que tengo 3 procesos tales que el pid1 + pid2 = pid3. Si los 3 incrementos y los decrementos pid3, el rastreador será 0 pero el recuento de ref es todavía 2 – hackworks

1

Fundamentalmente, el estado compartido de memoria compartida no es una solución robusta y no conozco una manera de robustecerla.

En última instancia, si un proceso finaliza, el sistema operativo limpia todos sus recursos no compartidos. Esto es, de paso, la gran ganancia de usar procesos (fork()) en lugar de hilos.

Sin embargo, los recursos compartidos no lo son. Los identificadores de archivo que otros tienen abiertos obviamente no están cerrados, y ... memoria compartida. Los recursos compartidos solo se cierran después de que finaliza el último proceso que los comparte.

Imagine que tiene una lista de PID en la memoria compartida. Un proceso podría escanear esta lista en busca de zombis, pero los PID pueden volver a utilizarse, o la aplicación podría haberse bloqueado en lugar de colgarse, o ...

Mi recomendación es que utilice tuberías u otras primitivas de paso de mensajes entre cada proceso (a veces hay una relación natural maestro-esclavo, otras veces todos necesitan hablar con todos). Luego, aprovecha el sistema operativo para cerrar estas conexiones cuando un proceso muere, por lo que sus compañeros reciben una señal en ese caso. Además, puede utilizar los mensajes de tiempo de espera de ping/pong para determinar si un par se ha colgado.

Si, después del perfilado, es demasiado ineficaz enviar los datos reales en estos mensajes, puede usar la memoria compartida para la carga siempre que mantenga el canal de control sobre algún tipo de flujo que el sistema operativo borre.

+0

+1 para la advertencia contra la optimización prematura (la respuesta a muchas preguntas sobre SO!) –

+0

Los casos de uso son raros :) Pero a veces realmente necesito unos pocos procesos para compartir un blob del tamaño de un gigabyte para trabajar cooperativamente - pero REALMENTE es la excepción, en casi todos los casos, IPC basado en eventos asíncronos es suficiente :) :) – yeoman

1

Los sistemas de seguimiento más eficientes para la propiedad de recursos ni siquiera usan recuentos de referencia, y mucho menos listas de titulares de referencias. Simplemente tienen información estática sobre los diseños de cada tipo de datos que pueden existir en la memoria, también la forma del marco de la pila para cada función, y cada objeto tiene un indicador de tipo. Entonces, una herramienta de depuración puede escanear la pila de cada hilo y seguir las referencias a los objetos recursivamente hasta que tenga un mapa de todos los objetos en la memoria y cómo se refieren el uno al otro. Pero, por supuesto, los sistemas que tienen esta capacidad también tienen recolección automática de basura de todos modos. Necesitan ayuda del compilador para obtener toda la información sobre el diseño de los objetos y los marcos de la pila, y dicha información no puede obtenerse confiablemente de C/C++ en todos los casos (porque las referencias de los objetos se pueden almacenar en uniones, etc.). Además, funcionan mucho mejor que el recuento de referencias en tiempo de ejecución.

Según su pregunta, en el caso "degenerado", todos (o casi todos) el estado de su proceso se mantendrían en la memoria compartida, además de las variables locales en la pila. Y en ese punto tendría el equivalente exacto de un programa multiproceso en un solo proceso. O para decirlo de otra manera, los procesos que comparten suficiente memoria comienzan a ser indistinguibles de los hilos.

Esto implica que no necesita especificar la parte de "múltiples procesos, memoria compartida" de su pregunta. Usted enfrenta el mismo problema que cualquier persona cuando intenta usar el conteo de referencias. Aquellos que usan hilos (o hacen uso irrestricto de la memoria compartida, lo mismo) enfrentan otro conjunto de problemas. Junta los dos y tendrás un mundo de dolor.

En términos generales, es un buen consejo no compartir objetos mutables entre subprocesos, cuando sea posible. Un objeto con un recuento de referencia es mutable, porque el recuento se puede modificar. En otras palabras, está compartiendo objetos mutables entre hilos (efectivos).

Yo diría que si su uso de memoria compartida es lo suficientemente complejo como para necesitar algo parecido a GC, entonces casi ha obtenido lo peor de ambos mundos: la costosa creación de procesos sin las ventajas del aislamiento de procesos. Ha escrito (en efecto) una aplicación de subprocesos múltiples en la que está compartiendo objetos mutables entre subprocesos.

Los sockets locales son una API muy multiplataforma y muy rápida para la comunicación entre procesos; el único que funciona básicamente idénticamente en todos los Unices y Windows. Así que considere usar eso como un canal de comunicación mínimo.

Por cierto, ¿está utilizando consistentemente el uso de punteros inteligentes en los procesos que contienen referencias? Esa es su única esperanza de obtener el conteo de referencias, incluso la mitad de lo correcto.

+0

Uso el recuento de referencias para entidades mutables compartidas y valores inmutables en proceso y sockets para eventos asyn basados ​​en todo hora. Por cierto. El recuento de referencias no hace que las instancias compartidas sean semánticamente mutables porque las operaciones atómicas lo hacen funcionar. – yeoman

+0

Pero de vez en cuando, tengo que compartir un blob del tamaño de un gigabyte entre los procesos para trabajar en colaboración con él. Y en esas raras ocasiones, el recuento de referencias entre procesos funciona como un amuleto, hasta que no lo hace. Entonces, la situación debe ser depurada :) Por lo tanto, creo que el requisito es raro, pero de ninguna manera exótico :) :) – yeoman

0

Uso siguiente

Incremento

do 
    find i such pid[i]=0 // optimistic 
while(cas[pids[i],0,mypid)==false) 
my_pos = i; 
atomic_inc(counter) 

decremento

pids[my_pos]=0 
atomic_dec(counter); 

Así que ya saben todos los procesos que utilizan este objeto

Usted MAX_PROCS lo suficientemente grande y buscar lugar al azar por lo que si el número de procesos significativamente más bajos que MAX_PROCS la búsqueda sería muy rápido.

0

Al lado de hacer las cosas usted mismo: también puede usar alguna herramienta como AQTime que tenga un memchecker de referencia contado.

Cuestiones relacionadas