Recientemente he estado trabajando en proyectos que utilizan mucho el subproceso. Creo que estoy bien en diseñarlos; utilizar el diseño sin estado tanto como sea posible, bloquear el acceso a todos los recursos que más de un hilo necesita, etc. Mi experiencia en programación funcional me ha ayudado enormemente.
Sin embargo, cuando leo el código de hilo de otras personas, me confundo. Estoy depurando un punto muerto en este momento, y dado que el estilo y el diseño de la codificación son diferentes de mi estilo personal, estoy teniendo dificultades para ver las posibles condiciones de punto muerto.
¿Qué buscas al depurar puntos muertos?
debugging
multithreading
Michael K
fuente
fuente
Respuestas:
Si la situación es un punto muerto real (es decir, dos subprocesos tienen dos bloqueos diferentes, pero al menos un subproceso quiere un bloqueo que retenga el otro subproceso), primero debe abandonar todas las concepciones previas de cómo los subprocesos ordenan el bloqueo. Asumir nada. Es posible que desee eliminar todos los comentarios del código que está viendo, ya que esos comentarios pueden hacer que crea algo que no es cierto. Es difícil enfatizar esto lo suficiente: no asumas nada.
Después de eso, determine qué bloqueos se mantienen mientras un hilo intenta bloquear algo más. Si puede, asegúrese de que un hilo se desbloquee en orden inverso al bloqueo. Aún mejor, asegúrese de que un hilo contenga solo un bloqueo a la vez.
Trabaje minuciosamente a través de la ejecución de un hilo y examine todos los eventos de bloqueo. En cada bloqueo, determine si un subproceso contiene otros bloqueos y, de ser así, en qué circunstancias otro subproceso, que realiza una ruta de ejecución similar, puede llegar al evento de bloqueo en consideración.
Es ciertamente posible que no encuentre el problema antes de que se le acabe el tiempo o el dinero.
fuente
Como otros han dicho ... si puede obtener información útil para iniciar sesión, intente eso primero, ya que es lo más fácil de hacer.
Identifique las cerraduras que están involucradas. Cambie todos los mutex / semáforos que esperan para siempre a esperas programadas ... algo ridículamente largo como 5 minutos. Registre el error cuando se agote el tiempo de espera. Esto al menos lo guiará en la dirección de uno de los bloqueos involucrados en el problema. Dependiendo de la variabilidad del tiempo, puede tener suerte y encontrar ambos bloqueos después de unas pocas carreras. Use las condiciones / códigos de falla de la función para registrar un seguimiento de pseudoapila después de que la espera temporizada no identifique cómo llegó allí en primer lugar. Esto debería ayudarlo a identificar el hilo que está involucrado en el problema.
Otra cosa que podría intentar es construir una biblioteca de envoltura alrededor de sus servicios de mutex / semáforo. Rastree qué hilos tienen cada mutex y qué hilos están esperando en el mutex. Cree un hilo de monitor que verifique cuánto tiempo los hilos han estado bloqueando. Active una duración razonable y descargue la información de estado que está rastreando.
En algún momento, será necesaria una simple inspección de código antiguo.
fuente
El primer paso (como dice Péter) es iniciar sesión. Aunque en mi experiencia esto a menudo es problemático. En el procesamiento paralelo pesado, esto a menudo no es posible. Tuve que depurar algo similar con una red neuronal una vez, que procesaba 100k de nodos por segundo. El error ocurrió solo después de varias horas e incluso una sola línea de salida desaceleró tanto las cosas que habría llevado días. Si es posible iniciar sesión, concéntrese menos en los datos, pero más en el flujo del programa, hasta que sepa en qué parte sucede. Solo una línea simple al comienzo de cada función y, si puede encontrar la función correcta, divídala en fragmentos más pequeños.
Otra opción es eliminar partes del código y los datos para localizar el error. Tal vez incluso escriba algún pequeño programa que tome solo algunas de las clases y ejecute solo las pruebas más básicas (todavía en varios hilos, por supuesto). Elimine todo lo relacionado con la interfaz gráfica de usuario, por ejemplo, cualquier resultado sobre el estado de procesamiento real. (Me pareció que la interfaz de usuario es la fuente del error con bastante frecuencia)
En su código, intente seguir el flujo lógico completo de control entre inicializar el bloqueo y liberarlo. Un error común podría ser bloquear al comienzo de una función, desbloquear al final, pero tener una declaración de retorno condicional en algún punto intermedio. Las excepciones también podrían evitar la liberación.
fuente
Mis mejores amigos han sido declaraciones de impresión / registro en lugares interesantes dentro del código. Esto generalmente me ayuda a comprender mejor lo que realmente está sucediendo dentro de la aplicación, sin interrumpir el tiempo entre diferentes hilos, lo que podría evitar la reproducción del error.
Si eso falla, mi único método restante es mirar el código e intentar construir un modelo mental de los diversos hilos e interacciones, y tratar de pensar en posibles formas locas de lograr lo que aparentemente ha sucedido :-) Pero no lo hago me considero un asesino de estancamientos muy experimentado. Esperemos que otros puedan dar mejores ideas, de las cuales yo también pueda aprender :-)
fuente
En primer lugar, intente obtener el autor de ese código. Probablemente tendrá la idea de lo que había escrito. incluso si ustedes dos no pueden identificar el problema con solo hablar, al menos pueden sentarse con él para identificar la porción de punto muerto, que será mucho más rápido de lo que entienden su código sin ayuda.
De lo contrario, como dijo Péter Török, el registro probablemente sea el camino. Hasta donde yo sé, Debugger hizo un mal trabajo en un entorno de subprocesos múltiples. trate de ubicar dónde está la cerradura, obtenga un conjunto de los recursos que están esperando y en qué condición se produce la condición de carrera.
fuente
Esta pregunta me atrae;) En primer lugar, considérese afortunado ya que pudo reproducir el problema de manera consistente en cada ejecución. Si recibe la misma excepción con el mismo stacktrace cada vez, entonces debería ser bastante sencillo. Si no es así, entonces no confíe tanto en el stacktrace, solo monitoree el acceso a los objetos globales y sus cambios de estado durante la ejecución.
fuente
Si tiene que depurar puntos muertos, ya está en problemas. Como regla general, use cerraduras por el menor tiempo posible, o no use ninguno, si es posible. Debe evitarse cualquier situación en la que bloquee y luego pase a un código no trivial.
Por supuesto, depende de su entorno de programación, pero debe mirar cosas como colas secuenciales que pueden permitirle acceder a un recurso desde un solo hilo.
Y luego hay una estrategia antigua pero infalible: Asigna un "nivel" a cada cerradura, comenzando en el nivel 0. Si tomas una cerradura de nivel 0, no tienes permitido ninguna otra cerradura. Después de tomar un bloqueo de nivel 1, puede tomar un bloqueo de nivel 0. Después de tomar un candado de nivel 10, puede tomar candados en los niveles 9 o inferiores, etc.
Si le resulta imposible hacerlo, debe corregir su código porque se encontrará con puntos muertos.
fuente