Prueba de condiciones de carrera de subprocesos múltiples


54

Leyendo los comentarios a esta respuesta , específicamente:

El hecho de que no puedas escribir un examen no significa que no esté roto. Comportamiento indefinido que generalmente funciona como se esperaba (C y C ++ están llenos de eso), condiciones de carrera, posible reordenamiento debido a un modelo de memoria débil ... - CodesInChaos hace 7 horas

@CodesInChaos si no se puede reproducir, entonces el código escrito para 'arreglar' tampoco se puede probar. Y poner el código no probado en vivo es un crimen peor en mi opinión - RhysW hace 5 horas

... me hace preguntarme si hay alguna buena forma general de desencadenar constantemente problemas de producción muy poco frecuentes causados ​​por las condiciones de carrera en el caso de prueba.


1
paso a paso (ensamblaje) instrucción por instrucción en ambos extremos
monstruo de trinquete

1
El análisis estático a menudo puede mostrar UB potencial, aunque no está claro si esto se cuenta como prueba
jk.

Lamento preguntar, pero ¿qué significa 'UB'?
Doug

2
Buena pregunta, sería interesante ver las posibles soluciones a esto.
RhysW

1
@Doug Comportamiento indefinido, que puede incluir, entre otros, las condiciones de carrera
jk.

Respuestas:


85

Después de haber estado en este negocio loco desde aproximadamente 1978, haber pasado casi todo ese tiempo en computación integrada en tiempo real, trabajando en sistemas multitarea, multiproceso, lo que sea, a veces con múltiples procesadores físicos, después de haber perseguido más de mi parte de la raza. condiciones, mi opinión considerada es que la respuesta a su pregunta es bastante simple.

No.

No hay una buena forma general de desencadenar una condición de carrera en las pruebas.

Su ÚNICA esperanza es diseñarlos completamente fuera de su sistema.

Cuando y si descubres que alguien más ha metido uno, debes replantearle un hormiguero y luego rediseñarlo para eliminarlo. Después de haber diseñado su faux pas (pronunciado f *** up) fuera de su sistema, puede ir a liberarlo de las hormigas. (Si las hormigas ya lo han consumido, dejando solo huesos, coloque un letrero que diga "¡Esto es lo que le sucede a las personas que ponen las condiciones de carrera en el proyecto XYZ!" Y DEJELO AQUÍ).


22
Estoy completamente de acuerdo. En otras palabras, esto es muy parecido al chiste: Paciente: "Doctor, me duele cuando hago esto ..." Doctor: "¡Entonces deja de hacerlo!"
Mark Rushakoff

Buena respuesta. Si algo causa un problema no comprobable, intente solucionarlo para comenzar, ¡evite el problema por completo!
RhysW

Mi única pregunta es: ¿Qué tan grande debería ser un hormiguero? (+1 BTW).
Peter K.

15
+1 para la pronunciación correcta de faux pas . (Y el resto de la respuesta.)
Blrfl

1
@PeterK., Este es uno de esos pocos casos en el desarrollo de software, junto con monitores, RAM y unidades de disco, donde más grande es mejor.
John R. Strohm

16

Si estás en la cadena de herramientas ms. La investigación de Ms ha creado una herramienta que forzará nuevas intercalaciones para cada carrera y puede recrear carreras fallidas llamadas ajedrez .

Aquí hay un video que lo muestra en uso.


55
Eso se ve impresionante; Tendré que encontrar tiempo para probarlo en algún momento.
Dan Neely

16

La mejor herramienta que conozco para este tipo de problemas es una extensión de Valgrind llamada Helgrind .

Básicamente, Valgrind simula un procesador virtual y ejecuta su binario (sin modificar) encima, para que pueda verificar cada acceso a la memoria. Usando ese marco, el sistema de vigilancia Helgrind llama a inferir cuando un acceso a una variable compartida no está protegido adecuadamente por un mecanismo de exclusión mutua. De esa manera, puede detectar una condición de carrera teórica, incluso si no ha sucedido realmente.

Intel vende una herramienta muy similar llamada Intel Inspector .

Estas herramientas dan excelentes resultados, pero su programa será considerablemente más lento durante el análisis.


1
¿Valgrind sigue siendo una herramienta única * nix?
Dan Neely

1
Sí, Linux, MacOSX, Android y algunos BSD: valgrind.org/info/platforms.html
Julien

1
ThreadSanitizer es una herramienta similar. Funciona de manera diferente a Helgrind, lo que le da la ventaja de ser mucho más rápido, pero requiere integración en la cadena de herramientas.
Sebastian Redl

7

Exponer un error de subprocesos múltiples requiere forzar diferentes subprocesos de ejecución para realizar sus pasos en un orden intercalado particular. Por lo general, esto es difícil de hacer sin la depuración manual o la manipulación del código para obtener algún tipo de "control" para controlar este entrelazado. Pero cambiar el código que se comporta de manera impredecible a menudo influirá en esa imprevisibilidad, por lo que es difícil de automatizar.

Jaroslav Tulach describe un buen truco en Practical API Design : si tiene declaraciones de registro en el código en cuestión, manipule al consumidor de esas declaraciones de registro (por ejemplo, un pseudo terminal inyectado) para que acepte los mensajes de registro individuales en un determinado orden basado en su contenido. Esto le permite controlar la intercalación de pasos en diferentes subprocesos sin tener que agregar nada al código de producción que aún no está allí.


2
He hecho algo similar antes de usar el repositorio inyectado para dormir los hilos que lo llaman en órdenes específicas para forzar la intercalación que quiero. Habiendo escrito un código que lo hace, me inclino a +1 @ la respuesta de John arriba. En serio, estas cosas son tan dolorosas de emplear correctamente, y todavía dan las mejores garantías de conjetura porque podría haber intercalaciones ligeramente diferentes con resultados diferentes; el mejor enfoque es eliminar todas las condiciones de carrera posibles mediante un análisis estático o un cuidadoso peinado del código para todos y cada uno de los estados compartidos
Jimmy Hoffa

6

No hay forma de estar absolutamente seguros de que no existan varios tipos de comportamiento indefinido (en particular las condiciones de carrera).

Sin embargo, hay una serie de herramientas que muestran una buena cantidad de tales situaciones. Es posible que pueda probar que existe un problema actualmente con tales herramientas, aunque no puede probar que su solución es válida.

Algunas herramientas interesantes para este propósito:

Valgrind es un verificador de memoria. Encuentra pérdidas de memoria, lecturas de memoria no inicializada, usos de punteros colgantes y accesos fuera de límites.

Helgrind es un verificador de seguridad de hilos. Encuentra condiciones de carrera.

Ambos funcionan mediante instrumentación dinámica, es decir, toman su programa tal cual y lo ejecutan en un entorno virtualizado. Esto los hace no intrusivos, pero lentos.

UBSan es un verificador de comportamiento indefinido. Encuentra varios casos de comportamiento indefinido de C y C ++, como desbordamientos de enteros, cambios fuera de rango y cosas similares.

MSan es un verificador de memoria. Tiene objetivos similares a los de Valgrind.

TSan es un verificador de seguridad de hilos. Tiene objetivos similares a Helgrind.

Estos tres están integrados en el compilador de Clang y generan código en tiempo de compilación. Esto significa que debe integrarlos en su proceso de compilación (en particular, debe compilar con Clang), lo que los hace mucho más difíciles de configurar inicialmente que * grind, pero por otro lado tienen una sobrecarga de tiempo de ejecución mucho menor.

Todas las herramientas que enumeré funcionan en Linux y algunas de ellas en MacOS. No creo que ningún trabajo en Windows sea confiable todavía.


1

Parece que la mayoría de las respuestas aquí confunden esta pregunta con "¿cómo detecto automáticamente las condiciones de carrera?" cuando la pregunta es realmente "¿cómo reproduzco las condiciones de carrera en las pruebas cuando las encuentro?"

La forma de hacerlo es introducir la sincronización en su código que se usa solo para pruebas. Por ejemplo, si ocurre una condición de carrera cuando ocurre el Evento X entre el Evento A y el Evento B, entonces para probar su aplicación, escriba un código que espere a que ocurra el Evento X después de que ocurra el Evento A. Es probable que necesite alguna forma para que sus pruebas hablen con su aplicación para decirle ("oye, estoy probando esto, así que espere este evento en esta ubicación").

Estoy usando node.js y mongo, donde algunas acciones implican la creación de datos consistentes en múltiples colecciones. En estos casos, las pruebas de mi unidad harán una llamada a la aplicación para decirle "configurar una espera para el evento X", y una vez que la aplicación lo haya configurado, se ejecutará la prueba para el evento X, y las pruebas le indicarán posteriormente la aplicación ("terminé con la espera del Evento X") para que el resto de las pruebas se ejecuten normalmente.

La respuesta aquí explica este tipo de cosas en detalle en el contexto de python: https://stackoverflow.com/questions/19602535/how-can-i-reproduce-the-race-conditions-in-this-python-code- seguramente

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.