2009-01-31 18 views
54

Esta es una continuación de this question, donde no recibí ninguna información sobre este punto. Aquí está la breve pregunta:¿Cómo detectar y solucionar problemas de multihilo?

¿Es posible detectar y solucionar problemas provenientes del código de subprocesos múltiples?

A menudo tenemos que decirle a nuestros clientes: "No podemos reproducir el problema aquí, así que no podemos solucionarlo. Díganos los pasos para reproducir el problema, luego lo arreglaremos". Es una respuesta desagradable si sé que es un problema de subprocesos múltiples, pero la mayoría no lo hago. ¿Cómo puedo saber que un problema es un problema de subprocesos múltiples y cómo depurarlo?

Me gustaría saber si hay marcos de registro especiales, o técnicas de depuración, o inspectores de código, o cualquier otra cosa para ayudar a resolver estos problemas. Los enfoques generales son bienvenidos. Si alguna respuesta debe estar relacionada con el idioma, guárdela en .NET y Java.

Respuesta

68

Problemas de subprocesamiento/concurrencia son muy difíciles de replicar, que es una de las razones por las que debe diseñar para evitar o al menos minimizar las probabilidades. Esta es la razón por la cual los objetos inmutables son tan valiosos. Intente aislar objetos mutables en un solo hilo y luego controle cuidadosamente el intercambio de objetos mutables entre hilos. Intente programar con un diseño de entrega de objetos en lugar de objetos "compartidos". Para este último, utilice objetos de control totalmente sincronizados (que son más fáciles de razonar), y evite que un objeto sincronizado utilice otros objetos que también deben estar sincronizados, es decir, intente mantenerlos autónomos. Tu mejor defensa es un buen diseño.

Los bloqueos son los más fáciles de depurar, si puede obtener un seguimiento de pila cuando está bloqueado. Dada la traza, la mayoría de los cuales detectan interbloqueos, es fácil precisar el motivo y luego razonar sobre el motivo y cómo solucionarlo. Con interbloqueos, siempre va a ser un problema adquirir los mismos bloqueos en diferentes órdenes.

Los bloqueos en vivo son más difíciles de detectar, ya que observar el sistema mientras está en el estado de error es su mejor opción.

Las condiciones de carrera tienden a ser extremadamente difíciles de replicar, y son aún más difíciles de identificar a partir de la revisión manual del código. Con estos, el camino que suelo tomar, además de las extensas pruebas para replicar, es razonar sobre las posibilidades e intentar registrar información para probar o refutar teorías. Si tiene evidencia directa de corrupción estatal, es posible que pueda razonar sobre las posibles causas basadas en la corrupción.

Cuanto más complejo es el sistema, más difícil es encontrar errores de concurrencia y razonar sobre su comportamiento. Haga uso de herramientas como JVisualVM y los perfiles de conexión remota: pueden ser un salvavidas si puede conectarse a un sistema en estado de error e inspeccionar los hilos y objetos.

Además, tenga en cuenta las diferencias en el comportamiento posible que dependen de la cantidad de núcleos de CPU, tuberías, ancho de banda del bus, etc. Los cambios en el hardware pueden afectar su capacidad de replicar el problema. Algunos problemas solo se mostrarán en otros CPU de un solo núcleo solo en núcleos múltiples.

Una última cosa, intente utilizar objetos de concurrencia distribuidos con las bibliotecas del sistema - por ejemplo, en Java java.util.concurrent es su amigo. Escribir sus propios objetos de control de concurrencia es difícil y plagado de peligros; déjalo en manos de los expertos, si tienes opción.

+4

Hay herramientas que pueden detectar condiciones de carrera. Para .NET, puede echar un vistazo a [CHESS] (http://research.microsoft.com/en-us/projects/chess/) de Microsoft, que intenta detectar las condiciones de carrera ejecutando el código para cada intercalado posible. Para Java, puede obtener [ThreadSafe] (http://www.contemplateltd.com/threadsafe) que es una herramienta específica para detectar y corregir errores de simultaneidad. –

+0

FindBugs es también una buena herramienta de análisis estático para encontrar posibles errores de subprocesamiento. –

7

Pensé que el answer que llegó a su other question era bastante bueno. Pero enfatizaré estos puntos.

Sólo modificar estado compartido en una sección crítica (Exclusión Mutua)

cerraduras adquirir en un orden establecido y liberarlos en el orden opuesto.

Use abstracciones pre-construidos siempre que sea posible (igual que la materia en java.util.concurrent)

Además, algunas herramientas de análisis pueden detectar algunos problemas potenciales. Por ejemplo, FindBugs puede encontrar algunos problemas de subprocesamiento en programas Java. Tales herramientas no pueden encontrar todos los problemas (no son balas de plata) pero pueden ayudar.

Como vanslly señala en un comentario a esta respuesta, estudiar la salida de registro bien ubicada también puede ser muy útil, pero tenga cuidado con Heisenbugs.

+0

¿Qué hay de la captura del estado erróneo o excepciones. ¿Como el registro de excepción, e incluyendo el seguimiento de la pila? – Llyle

4

Suponiendo que tengo informes de problemas que son difíciles de reproducir, siempre los encuentro leyendo códigos, preferiblemente lectura de códigos de pares, para que pueda analizar las necesidades de subprocesos de semántica/bloqueo. Cuando hacemos esto en base a un problema reportado , encuentro que siempre identificamos uno o más problemas con bastante rapidez. Creo que también es una técnica bastante barata para resolver problemas difíciles.

Perdón por no poder decirle que presione ctrl + shift + f13, pero no creo que haya nada de eso disponible. Pero solo pensando en , el problema reportado en realidad es por lo general da un sentido de dirección bastante fuerte en el código, por lo que no tiene que comenzar en main().

+0

Sí, hablar sobre qué código debería hacer y qué está haciendo realmente ayuda mucho. – MicSim

1

Visual Studio le permite inspeccionar la pila de llamadas de cada hilo, y puede cambiar entre ellas. De ninguna manera es suficiente para rastrear todo tipo de problemas de enhebrado, pero es un comienzo. Se planean muchas mejoras para la depuración de subprocesos múltiples para el próximo VS2010.

He usado WinDbg + SoS para enhebrar problemas en el código .NET. Puede inspeccionar bloqueos (blokcs de sincronización), pilas de llamadas de subprocesos, etc.

+0

SoS es una DLL que habilita la depuración administrada en WinDbg. –

4

Además de las otras buenas respuestas que ya tiene: Siempre pruebe en una máquina con al menos tantos procesadores/núcleos de procesador como los usa el cliente, o como hay hilos activos en su programa. De lo contrario, algunos errores de subprocesamiento múltiple pueden ser difíciles o imposibles de reproducir.

5

Además de los volcados de emergencia, una técnica es el extenso registro en tiempo de ejecución: donde cada hilo registra lo que está haciendo.

La primera pregunta cuando se informa un error, entonces, podría ser "¿Dónde está el archivo de registro?"

A veces puede ver el problema en el archivo de registro: "Este hilo está detectando un estado ilegal/inesperado aquí ... y mire, este otro hilo estaba haciendo eso, justo antes y/o después de esto."

Si el archivo de registro no dice lo que está sucediendo, discúlpese con el cliente, agregue suficientes declaraciones de registro adicionales al código, proporcione el nuevo código al cliente y diga que lo arreglará después lo que sucede una vez más.

+0

El análisis de problemas multiproceso con un archivo de registro a menudo cambia demasiado el comportamiento dinámico del programa. Es mejor utilizar una traza en la memoria y copiar la copia de la traza de memoria solo en un archivo una vez que se ha producido el problema. Ver más detalles en mi respuesta. –

-6

lo mejor que se me ocurre es que se mantenga alejado de código multihilo siempre que sea posible. parece que hay son muy pocos los programadores que pueden escribir aplicaciones multihilos sin errores y yo diría que no hay codificadores que puedan escribir errores grande multi aplicaciones con hilos

+0

Bueno, ¿cómo sabes que cualquier aplicación grande (de una o varias hebras) está libre de errores? No puedes. Pero aparte de eso, creo que es posible escribir código multihilo relativamente seguro, si conoce y obedece las reglas. Sin embargo, la mayor parte del tiempo tratando de obtener un mejor rendimiento introducirá errores. – mghie

+6

¡Ahora existe la actitud correcta! Vamos a rendirnos porque es difícil; A quién le importan los órdenes de magnitud equivale a posibles ganancias de rendimiento que mi aplicación podría sacar de un momento de reflexión. Porque todos sabemos que las aplicaciones de un solo hilo no tienen errores: P – MatthewJ

+0

@MatthewJ exactamente. A veces los riesgos no valen la pena. Mucha gente piensa lo mismo sobre la energía nuclear. Lo hago sobre el código multiproceso. – max

1

assert() es su amigo para detectar condiciones de carrera. Siempre que ingrese a una sección crítica, afirme que la invariante asociada con ella es verdadera (para eso están las CS). Aunque, lamentablemente, el cheque puede ser costoso y, por lo tanto, no adecuado para su uso en el entorno de producción.

5

Para Java hay una herramienta de verificación llamada javapathfinder que me parece útil para depurar y verificar la aplicación multi-threading contra posibles condiciones de carrera y errores de bloqueo de la muerte del código.
Funciona perfectamente con Eclipse y Netbean IDE.

0

Me enfrenté a un problema de hilo que daba el MISMO resultado erróneo y no se comportaba de manera imprevisible ya que cada vez otras condiciones (memoria, programador, carga de procesamiento) eran más o menos las mismas.

Desde mi experiencia, puedo decir que LO MÁS DIFÍCIL es reconocer que se trata de un problema de subprocesos, y MEJOR SOLUCIÓN es revisar cuidadosamente el código de subprocesos múltiples. Simplemente mirando cuidadosamente el código de la secuencia, debe intentar averiguar qué puede salir mal. Otras formas (tirada de hilo, perfilador, etc.) serán las segundas.

1

Implementé la herramienta vmlens para detectar las condiciones de carrera en los programas Java durante el tiempo de ejecución. Implementa un algoritmo llamado eraser.

+0

Advanced Eraser? –

+0

En realidad, cambié el algoritmo. vmlens ahora analiza el pasado antes de la relación para todos los campos accedidos simultáneamente. –

3

A veces, las soluciones multiproceso no se pueden evitar. Si hay un error, necesita ser investigado en tiempo real, lo cual es casi imposible con la mayoría de las herramientas como Visual Studio. La única solución práctica es escribir trazas, aunque el trazado en sí debe:

  1. no añadir ningún retraso
  2. No utilice ningún bloqueo
  3. ser multihilo segura
  4. traza lo que sucedió en la secuencia correcta.

Esto suena como una tarea imposible, pero se puede lograr fácilmente escribiendo la traza en la memoria. En C#, se vería algo como esto:

public const int MaxMessages = 0x100; 
string[] messages = new string[MaxMessages]; 
int messagesIndex = -1; 

public void Trace(string message) { 
    int thisIndex = Interlocked.Increment(ref messagesIndex); 
    messages[thisIndex] = message; 
} 

El método trace() es multi-hilo de seguridad no bloqueante, y se puede llamar desde cualquier hilo. En mi PC, lleva unos 2 microsegundos ejecutar, lo que debería ser lo suficientemente rápido.

Agregue instrucciones de Trace() donde crea que algo podría ir mal, deje que el programa se ejecute, espere hasta que ocurra el error, detenga el rastreo y luego investigue el rastreo para detectar cualquier error.

Una descripción más detallada de este enfoque, que además contiene la información de temas y el tiempo, recicla el tampón y da salida a la traza bien se puede encontrar en: CodeProject: Depuración de código multiproceso en tiempo real 1

1

Una pequeña carta con algunas técnicas de depuración para tener en cuenta al depurar código multiproceso. El gráfico está creciendo, deje comentarios y sugerencias para agregar. (Archivo de actualización en this link)

Multithreaded debugging chart

-2

estoy usando GNU y usar script sencillo

$ más gdb_tracer

b func.cpp:2871 
r 
#c 
while (1) 
next 
#step 
end 
Cuestiones relacionadas