2008-10-23 13 views
6

Encontré un problema al ejecutar algún código anterior que me fue transmitido. Funciona el 99% del tiempo, pero de vez en cuando, noto que arroja una excepción de "Violación de ubicación de lectura". Tengo un número variable de subprocesos que pueden ejecutar este código durante la vida del proceso. La baja frecuencia de aparición puede ser indicativa de una condición de carrera, pero no sé por qué se causaría una excepción en este caso. Aquí está el código en cuestión:Localización de lectura de violación en el operador de std :: map []

MyClass::Dostuff() 
{ 
    static map<char, int> mappedChars; 
    if (mappedChars.empty()) 
    { 
     for (char c = '0'; c <= '9'; ++c) 
     { 
      mappedChars[c] = c - '0'; 
     } 
    } 
    // More code here, but mappedChars in not changed. 
} 

La excepción en la aplicación del operador [] del mapa, en la primera llamada al operador [] (Uso de la aplicación VS2005 de STL.)


mapped_type& operator[](const key_type& _Keyval) 
{ 
    iterator _Where = this->lower_bound(_Keyval); //exception thrown on the first line 
    // More code here 
} 

Ya probé la congelación de subprocesos en el operador [] y trato de que todos se ejecutaran al mismo tiempo, pero no pude reproducir la excepción con esa metodología.

¿Puedes pensar en alguna razón por la que eso arrojaría, y solo algunas veces?

(Sí, ya sé STL no es seguro para subprocesos y necesitaré para hacer cambios aquí. Estoy sobre todo curiosidad de por qué estoy viendo el comportamiento que he descrito anteriormente.)

Conforme a lo solicitado, aquí algunos detalles adicionales sobre la excepción:
Excepción no controlada en 0x00639a1c (app.exe) en app15-51-02-0944_2008-10-23.mdmp: 0xC0000005: Acceso a la ubicación de lectura de la infracción 0x00000004.

Gracias a todos por sugerir soluciones a problemas de subprocesamiento múltiple, pero esto no es lo que esta pregunta debe abordar. Sí, entiendo que el código presentado no está protegido correctamente y es excesivo en lo que está tratando de lograr. Ya tengo la solución implementada. Solo estoy tratando de comprender mejor por qué se lanzó esta excepción, para empezar.

+0

Conocer la dirección de la violación podría ser útil. Es posible que "esto" sea nulo y no tenga nada que ver con el mapa en sí. –

Respuesta

5

Dada una dirección de "4", es probable que el puntero "this" sea nulo o el iterador sea incorrecto. Debería poder ver esto en el depurador. Si esto es nulo, entonces el problema no está en esa función sino en quien llama esa función. Si el iterador es malo, entonces es la condición de carrera a la que aludiste. La mayoría de los iteradores no pueden tolerar la lista que se está actualizando.

Okay wait - No FM aquí. Los Static se inicializan en el primer uso. El código que hace esto no es seguro para múltiples hilos. un hilo está haciendo la inicialización, mientras que el segundo piensa que ya se ha hecho, pero todavía está en progreso. El resultado es que usa una variable no inicializada. Puede ver esto en el ensamblado siguiente:

static x y; 
004113ED mov   eax,dword ptr [$S1 (418164h)] 
004113F2 and   eax,1 
004113F5 jne   wmain+6Ch (41141Ch) 
004113F7 mov   eax,dword ptr [$S1 (418164h)] 
004113FC or   eax,1 
004113FF mov   dword ptr [$S1 (418164h)],eax 
00411404 mov   dword ptr [ebp-4],0 
0041140B mov   ecx,offset y (418160h) 
00411410 call  x::x (4111A4h) 
00411415 mov   dword ptr [ebp-4],0FFFFFFFFh 

El $ S1 se establece en 1 cuando se inicia. Si se establece, (004113F5) salta sobre el código de inicio; congelar los hilos en el fnc no ayudará porque esta comprobación se realiza al ingresar a una función. Esto no es nulo, pero uno de los miembros sí lo es.

Arregle moviendo el mapa fuera del método y dentro de la clase como estático. Luego se inicializará al inicio. De lo contrario, debe poner un CR alrededor de las llamadas DoStuff(). Puede protegerse de los problemas MT restantes colocando un CR alrededor del uso del mapa en sí (por ejemplo, DoStuff utiliza el operador []).

+0

¿Quieres decir que el mapa es este puntero? El mapa es estático y nunca se cambia fuera del ciclo for. – Marcin

+0

Sí, así es como se ve. Pero acepto que no tiene sentido si el único acceso al mapa es mappedChars [c]. El iterador podría ser lo que es nulo causado por las condiciones de la carrera: un iterador devuelto, el mapa actualizado por otro hilo que lo hace inválido. –

+0

@ me.yahoo.com: un problema es que el mapa estático necesita ser construido. El hilo A aparece y comienza a construirlo, pero antes de eso, el hilo B aparece, piensa que está construido y comienza a usarlo. Bam. Otro escenario es que diferentes hilos piensan que necesitan construir. Bam. –

2

Si varios hilos están invocando la función DoStuff esto significa que el código de inicialización

if (mappedChars.empty()) 

puede entrar en una condición de carrera. Esto significa que el hilo 1 ingresa a la función, encuentra el mapa vacío y comienza a llenarlo. El hilo 2 luego ingresa a la función y encuentra el mapa no vacío (pero no completamente inicializado), por lo que comienza a leerlo alegremente. Debido a que ambos hilos están ahora en disputa, pero uno está modificando la estructura del mapa (es decir, insertando nodos), se producirá un comportamiento indefinido (un bloqueo).

Si utiliza una primitiva de sincronización antes de comprobar el mapa para empty(), y se libera después de que se garantiza que el mapa se ha inicializado por completo, todo estará bien.

He echado un vistazo a través de Google y, de hecho, la inicialización estática es no hilo seguro. Por lo tanto, la declaración static mappedChars es un problema inmediato. Como otros han mencionado, sería mejor si su inicialización se realizó cuando solo 1 hilo está garantizado para estar activo durante toda la vida de inicialización.

+0

Tenga en cuenta que el hilo de escritura aún no ha comenzado a llenarlo. Lanza en la primera línea del operador []. Hasta donde yo sé, lower_bound no cambia el contenido del mapa. – Marcin

+0

¿Qué están haciendo los otros subprocesos? ¿Alguien más está accediendo también a esta variable compartida? No recuerdo si el código insertado por el compilador para variables estáticas es seguro para hilos. – Henk

3

mappedChars es estático, por lo que es compartido por todos los hilos que ejecutan DoStuff(). Solo eso podría ser tu problema.

Si tiene que usar un mapa estático, entonces puede necesitar protegerlo con un mutex o sección crítica.

Personalmente, creo que usar un mapa para este propósito es excesivo. Escribiría una función auxiliar que toma un carácter y resta '0' de él. No tendrá ningún problema de seguridad de subprocesos con una función.

1

Cuando entra en el multi-threading, generalmente hay demasiadas cosas para identificar el lugar exacto donde las cosas van mal, ya que siempre cambiará. Hay un montón de lugares donde el uso de un mapa estático en una situación multiproceso podría ir mal.

Consulte this thread para conocer algunas formas de proteger una variable estática. Su mejor opción sería llamar a la función una vez antes de iniciar múltiples hilos para inicializarla. O eso, o mueve el mapa estático, y crea un método de inicialización por separado.

0

¿Alguna vez llama a operator[] con un argumento que no está en el rango 0..9? Si es así, estás modificando inadvertidamente el mapa, lo que probablemente causa maldad en otros hilos. Si llama al operator[] con un argumento que no está ya en el mapa, inserta esa clave en el mapa con un valor igual al valor predeterminado del tipo de valor (0 en el caso de int).

Cuestiones relacionadas