2010-08-18 11 views
6

Este es mi seguimiento de la publicación anterior sobre problemas de administración de memoria. Los siguientes son los problemas que sé.Problemas relacionados con subprocesos y depuración de ellos

1) razas de datos (violaciónes atomicidad y la corrupción de datos)

2) ordenar problemas

3) haciendo un mal uso de las cerraduras que conduce a las cerraduras

4) heisenbug

cualquier otra cuestión con multi threading? ¿Cómo resolverlos?

Respuesta

2

La lista de cuatro problemas de Eric es bastante acertada. Pero la depuración de estos problemas es difícil.

Para el interbloqueo, siempre he preferido "bloqueos nivelados". Básicamente le das a cada tipo de cerradura un número de nivel. Y luego requiere que un hilo tenga bloqueos que sean monótonos.

Para cerraduras niveladas, se puede declarar una estructura como esta:

typedef struct { 
    os_mutex actual_lock; 
    int level; 
    my_lock *prev_lock_in_thread; 
} my_lock_struct; 

static __tls my_lock_struct *last_lock_in_thread; 

void my_lock_aquire(int level, *my_lock_struct lock) { 
    if (last_lock_in_thread != NULL) assert(last_lock_in_thread->level < level) 
    os_lock_acquire(lock->actual_lock) 
    lock->level = level 
    lock->prev_lock_in_thread = last_lock_in_thread 
    last_lock_in_thread = lock 
} 

Lo bueno de cerraduras niveladas es la posibilidad de estancamiento provoca una aserción. Y con un poco de magia adicional con FUNC y LINE sabes exactamente qué maldad hizo tu hilo.

Para las carreras de datos y la falta de sincronización, la situación actual es bastante pobre. Hay herramientas estáticas que intentan identificar problemas. Pero los falsos positivos son altos.

La empresa para la que trabajo (http://www.corensic.com) tiene un nuevo producto llamado Jinx que busca activamente casos en los que puedan exponerse las condiciones de carrera. Esto se hace mediante el uso de la tecnología de virtualización para controlar el entrelazado de subprocesos en las diversas CPU y el acercamiento de la comunicación entre las CPU.

Échale un vistazo. Probablemente tengas unos días más para descargar la versión beta gratis.

Jinx es particularmente bueno para encontrar errores en las estructuras de datos libres de bloqueo. También lo hace muy bien para encontrar otras condiciones de carrera. Lo bueno es que no hay falsos positivos. Si su prueba de código se acerca a una condición de carrera, Jinx ayuda al código a seguir el mal camino. Pero si el mal camino no existe, no se te darán advertencias falsas.

1

Los cuatro problemas más comunes con theading son

1-Deadlock
2-livelock
3-Race Condiciones
4-inanición

+1

Muchas gracias Pero, ¿cómo resolver estos problemas? – brett

+0

Todos estos problemas se pueden resolver con semáforos (bloqueos). Necesita comprender cuidadosamente lo que está haciendo primero. Trate de no sobrepasarlo con hilos, son una herramienta para ayudar a su programa a hacer cosas, no un truco mágico que necesita poner en todo el lugar – Eric

1

¿Cómo resolver [problemas con múltiples subprocesos] ?

Una buena forma de "depurar" las aplicaciones de MT es a través del registro. Una buena biblioteca de registro con amplias opciones de filtrado lo hace más fácil. Por supuesto, el registro en sí mismo influye en el tiempo, por lo que todavía puede tener "heisenbugs", pero es mucho menos probable que cuando está realmente entrando en el depurador.

Prepárelo y planee para eso. Incluya un buen recurso de registro en su aplicación desde el principio.

2

Desafortunadamente no hay una buena píldora que ayude a resolver automáticamente la mayoría de los problemas de enhebrado. Incluso las pruebas unitarias que funcionan tan bien en fragmentos de código único pueden no detectar nunca una condición de carrera extremadamente sutil.

Una cosa que ayudará a mantener los datos de interacción de subprocesos encapsulados en los objetos. Cuanto menor sea la interfaz/alcance del objeto, más fácil será detectar errores en la revisión (y posiblemente pruebas, pero las condiciones de carrera pueden ser difíciles de detectar en casos de prueba). Al mantener una interfaz simple que se puede usar, los clientes que usan la interfaz también serán correctos por defecto. Al construir un sistema más grande a partir de muchas piezas más pequeñas (de las cuales solo hay un puñado de interacciones entre subprocesos), puede evitar en gran medida los errores de subprocesamiento.

1

Haz tus hilos lo más simple posible.

Trate de no utilizar las variables globales. Las constantes globales (constantes reales que nunca cambian) están bien. Cuando necesite usar variables globales o compartidas, debe protegerlas con algún tipo de mutex/lock (semáforo, monitor, ...).

Asegúrese de que realmente entiende cómo funcionan sus mutexes. Hay algunas implementaciones diferentes que pueden funcionar de manera diferente.

Trate de organizar su código para que las secciones críticas (lugares donde tiene algún tipo de cerrojo (s)) sean lo más rápido posible. Tenga en cuenta que algunas funciones pueden bloquear (suspender o esperar algo y evitar que el sistema operativo permita que ese hilo continúe ejecutándose durante un tiempo). No los use mientras mantiene bloqueos (a menos que sea absolutamente necesario o durante la depuración ya que a veces puede mostrar otros errores).

Trate de entender lo que más hilos de hecho hace por usted.A ciegas, lanzar más hilos a un problema muy a menudo empeorará las cosas. Diferentes hilos compiten por la CPU y por los bloqueos.

La evasión de interbloqueo requiere planificación. Intente evitar tener que adquirir más de un bloqueo a la vez. Si esto es inevitable, decida un pedido que utilizará para adquirir y liberar los bloqueos de todos los hilos. Asegúrate de saber qué significa un punto muerto.

La depuración de aplicaciones multiproceso o distribuidas es difícil. Si puede hacer la mayor parte de la depuración en un único entorno con hebras (tal vez incluso forzando a que otros subprocesos duerman), entonces puede intentar eliminar los errores céntricos que no son de subprocesamiento antes de saltar a la depuración de subprocesos múltiples.

Siempre piense en lo que los otros hilos podrían estar haciendo. Comenta esto en tu código. Si está haciendo algo de cierta manera porque sabe que en ese momento no debería haber ningún otro subproceso que esté accediendo a un determinado recurso, escriba un comentario importante que lo diga.

Es posible que desee para envolver las llamadas a los bloqueos mutex/desbloquea en otras funciones como:

int my_lock_get (bloqueo lock_type, archivo * const char, línea, const char * msg) {

thread_id_type me = this_thread(); 

logf("%u\t%s (%u)\t%s:%u\t%s\t%s\n", time_now(), thread_name(me), me, "get", msg); 

lock_get(lock); 

logf("%u\t%s (%u)\t%s:%u\t%s\t%s\n", time_now(), thread_name(me), me, "in", msg); 

}

Y una versión similar para desbloquear. Tenga en cuenta que las funciones y los tipos utilizados en este conjunto están compuestos y no están basados ​​excesivamente en ninguna API.

Usando algo como esto puede volver si hay un error y usar un script perl o algo similar para ejecutar consultas en sus registros para examinar dónde salieron las cosas (cerraduras y desbloqueos correspondientes, por ejemplo).

Tenga en cuenta que su funcionalidad de impresión o registro puede necesitar bloqueos también. Muchas bibliotecas ya tienen esto integrado, pero no todas lo hacen. Estos bloqueos no necesitan utilizar la versión de impresión de las funciones lock_ [get | release] o tendrá recursión infinita.

1
  1. Cuidado con las variables globales, incluso si son const, en particular en C++. Solo los POD que están estáticamente inicializados "à la" C son buenos aquí. Tan pronto como entre en juego un constructor en tiempo de ejecución , sea extremadamente cuidadoso. Orden de inicialización de AFAIR de variables con enlace estático que están en unidades de compilación diferentes son llamadas en un orden indefinido. Tal vez clases de C++ que inicializan todos sus miembros correctamente y tienen un cuerpo de función vacío , podría estar bien hoy en día, pero una vez tuve una mala experiencia con eso también.

    Esta es una de las razones por qué en el lado POSIX pthread_mutex_t es mucho más fácil de programar que sem_t: que tiene un inicializador estático PTHREAD_MUTEX_INITIALIZER.

  2. Mantener las secciones críticas tan corto como sea posible , por dos razones: podría ser más eficientes en el final, pero más importante es que es más fácil de mantener y de depurar.

    Una sección crítica no debe ser nunca ya que una pantalla, incluyendo el bloqueo y desbloqueo que se necesita para protegerlo, e incluyendo los comentarios y afirmaciones que ayudan al lector a entender lo que está sucediendo .

    inicio implementar secciones críticas muy rígidamente quizás con un bloqueo global para todos ellos, y relajar las restricciones después.

  3. El registro puede ser difícil si muchos subprocesos comienzan a escribirse al mismo tiempo . Si cada hilo hace una cantidad de trabajo razonable de intente con haga que cada uno escriba un archivo de su propio, de modo que no entrelacen entre sí.

    Pero cuidado, el registro cambia el comportamiento del código. Esto puede ser malo cuando desaparecen los errores , o es beneficioso cuando aparecen los errores que de otra forma no se notarían .

    Para hacer un análisis post-mortem de tal lío que tiene que tener marcas de tiempo precisas en cada línea tal que todos los archivos pueden ser fusionaron y le dará una visión coherente de la ejecución.

1

-> Agregue inversión de prioridad a esa lista.

Como se eludió otro cartel, los archivos de registro son cosas maravillosas. Para los interbloqueos, usar un LogLock en lugar de un Lock puede ayudar a identificar cuándo las entidades dejan de funcionar. Es decir, una vez que sepa que tiene un punto muerto, el registro le dirá cuándo y dónde se crearon instancias y se liberaron los bloqueos. Esto puede ser de gran ayuda para rastrear estas cosas.

He encontrado que las condiciones de carrera cuando se utiliza un modelo Actor siguiendo el mismo mensaje-> confirmar-> confirmar el estilo recibido parecen desaparecer. Eso dijo, YMMV.

Cuestiones relacionadas