2010-06-14 9 views
7

Estoy usando Valgrind --tool = drd para verificar mi aplicación que usa Boost :: thread. Básicamente, la aplicación rellena un conjunto de valores de "Libro" con valores "Kehai" basados ​​en las entradas a través de una conexión de socket.No puedo averiguar dónde está ocurriendo la condición de raza

En un hilo separado, un usuario puede conectarse y recibir los libros que se le envían.

Es bastante simple, así que pensé que usar un boost :: mutex :: scoped_lock en la ubicación que serializa el libro y la ubicación que borra los datos del libro debería ser suficiente para evitar cualquier condición de carrera. Aquí está el código:

void Book::clear() 
    { 
    boost::mutex::scoped_lock lock(dataMutex); 
    for(int i =NUM_KEHAI-1; i >= 0; --i) 
    { 
     bid[i].clear(); 

     ask[i].clear(); 
    } 
    } 

    int Book::copyChangedKehaiToString(char* dst) const 
    { 
    boost::mutex::scoped_lock lock(dataMutex); 

    sprintf(dst, "%-4s%-13s",market.c_str(),meigara.c_str()); 
    int loc = 17; 
    for(int i = 0; i < Book::NUM_KEHAI; ++i) 
    { 
     if(ask[i].changed > 0) 
     { 
     sprintf(dst+loc,"A%i%-21s%-21s%-21s%-8s%-4s",i,ask[i].price.c_str(),ask[i].volume.c_str(),ask[i].number.c_str(),ask[i].postTime.c_str(),ask[i].status.c_str()); 
     loc += 77; 
     } 
    } 
    for(int i = 0; i < Book::NUM_KEHAI; ++i) 
    { 
     if(bid[i].changed > 0) 
     { 
     sprintf(dst+loc,"B%i%-21s%-21s%-21s%-8s%-4s",i,bid[i].price.c_str(),bid[i].volume.c_str(),bid[i].number.c_str(),bid[i].postTime.c_str(),bid[i].status.c_str()); 
     loc += 77; 
     } 
    } 

    return loc; 
    } 

La función clear() y la función copyChangedKehaiToString() se denominan en el hilo datagetting y envío de hilo, respectivamente datos. También, como una nota, el libro de clase:

struct Book 
    { 
    private: 
    Book(const Book&); Book& operator=(const Book&); 
    public: 

    static const int NUM_KEHAI=10; 
    struct Kehai; 
    friend struct Book::Kehai; 

    struct Kehai 
    { 
    private: 
     Kehai& operator=(const Kehai&); 
    public: 
     std::string price; 
     std::string volume; 
     std::string number; 
     std::string postTime; 
     std::string status; 

     int changed; 
     Kehai(); 
     void copyFrom(const Kehai& other); 
     Kehai(const Kehai& other); 
     inline void clear() 
     { 

     price.assign(""); 
     volume.assign(""); 
     number.assign(""); 
     postTime.assign(""); 
     status.assign(""); 
     changed = -1; 
     } 
    }; 

    std::vector<Kehai> bid; 
    std::vector<Kehai> ask; 
    tm recTime; 
    mutable boost::mutex dataMutex; 


    Book(); 
    void clear(); 
    int copyChangedKehaiToString(char * dst) const; 
     }; 

Al utilizar valgrind --tool = DRD, consigo errores condición de carrera como la de abajo:

==26330== Conflicting store by thread 1 at 0x0658fbb0 size 4 
==26330== at 0x653AE68: std::string::_M_mutate(unsigned int, unsigned int, unsigned int) (in /usr/lib/libstdc++.so.6.0.8) 
==26330== by 0x653AFC9: std::string::_M_replace_safe(unsigned int, unsigned int, char const*, unsigned int) (in /usr/lib/libstdc++.so.6.0.8) 
==26330== by 0x653B064: std::string::assign(char const*, unsigned int) (in /usr/lib/libstdc++.so.6.0.8) 
==26330== by 0x653B134: std::string::assign(char const*) (in /usr/lib/libstdc++.so.6.0.8) 
==26330== by 0x8055D64: Book::Kehai::clear() (Book.h:50) 
==26330== by 0x8094A29: Book::clear() (Book.cpp:78) 
==26330== by 0x808537E: RealKernel::start() (RealKernel.cpp:86) 
==26330== by 0x804D15A: main (main.cpp:164) 
==26330== Allocation context: BSS section of /usr/lib/libstdc++.so.6.0.8 
==26330== Other segment start (thread 2) 
==26330== at 0x400BB59: pthread_mutex_unlock (drd_pthread_intercepts.c:633) 
==26330== by 0xC59565: pthread_mutex_unlock (in /lib/libc-2.5.so) 
==26330== by 0x805477C: boost::mutex::unlock() (mutex.hpp:56) 
==26330== by 0x80547C9: boost::unique_lock<boost::mutex>::~unique_lock() (locks.hpp:340) 
==26330== by 0x80949BA: Book::copyChangedKehaiToString(char*) const (Book.cpp:134) 
==26330== by 0x80937EE: BookSerializer::serializeBook(Book const&, std::string const&) (BookSerializer.cpp:41) 
==26330== by 0x8092D05: BookSnapshotManager::getSnaphotDataList() (BookSnapshotManager.cpp:72) 
==26330== by 0x8088179: SnapshotServer::getDataList() (SnapshotServer.cpp:246) 
==26330== by 0x808870F: SnapshotServer::run() (SnapshotServer.cpp:183) 
==26330== by 0x808BAF5: boost::_mfi::mf0<void, RealThread>::operator()(RealThread*) const (mem_fn_template.hpp:49) 
==26330== by 0x808BB4D: void boost::_bi::list1<boost::_bi::value<RealThread*> >::operator()<boost::_mfi::mf0<void, RealThread>, boost::_bi::list0>(boost::_bi::type<void>, boost::_mfi::mf0<void, RealThread>&, boost::_bi::list0&, int) (bind.hpp:253) 
==26330== by 0x808BB90: boost::_bi::bind_t<void, boost::_mfi::mf0<void, RealThread>, boost::_bi::list1<boost::_bi::value<RealThread*> > >::operator()() (bind_template.hpp:20) 
==26330== Other segment end (thread 2) 
==26330== at 0x400B62A: pthread_mutex_lock (drd_pthread_intercepts.c:580) 
==26330== by 0xC59535: pthread_mutex_lock (in /lib/libc-2.5.so) 
==26330== by 0x80546B8: boost::mutex::lock() (mutex.hpp:51) 
==26330== by 0x805473B: boost::unique_lock<boost::mutex>::lock() (locks.hpp:349) 
==26330== by 0x8054769: boost::unique_lock<boost::mutex>::unique_lock(boost::mutex&) (locks.hpp:227) 
==26330== by 0x8094711: Book::copyChangedKehaiToString(char*) const (Book.cpp:113) 
==26330== by 0x80937EE: BookSerializer::serializeBook(Book const&, std::string const&) (BookSerializer.cpp:41) 
==26330== by 0x808870F: SnapshotServer::run() (SnapshotServer.cpp:183) 
==26330== by 0x808BAF5: boost::_mfi::mf0<void, RealThread>::operator()(RealThread*) const (mem_fn_template.hpp:49) 
==26330== by 0x808BB4D: void boost::_bi::list1<boost::_bi::value<RealThread*> >::operator()<boost::_mfi::mf0<void, RealThread>, boost::_bi::list0>(boost::_bi::type<void>, boost::_mfi::mf0<void, RealThread>&, boost::_bi::list0&, int) (bind.hpp:253) 

Para la vida de mí , no puedo entender dónde está la condición de carrera. Por lo que puedo decir, borrar el kehai se hace solo después de haber tomado el mutex, y lo mismo ocurre con copiarlo en una cadena. ¿Alguien tiene alguna idea de lo que podría estar causando esto o dónde debería mirar?

Gracias amablemente.

Respuesta

6

Después de su publicación me tomé el tiempo para aprender sobre Valgrind y cómo debe leerse su salida.

puedo ver lo siguiente:

se invoca Book::clear que a su vez llama a Book::Kehai::clear, donde se asigna un valor a una cadena. Dentro del std::string::assign, el STL hace algo que almacena algún valor en la dirección 0x0658fbb0.

Mientras tanto, el otro hilo ha accedido a la misma ubicación de memoria, por lo tanto, esta situación se considera una condición de carrera.

Ahora mire el "contexto" del otro hilo. Valgrind no muestra su ubicación exacta de pila, sin embargo muestra entre qué "segmentos" se ha producido. Según Valgrind, un segmento es un bloque consecutivo de accesos de memoria delimitados por operaciones de sincronización.

vemos que este bloque comienza conpthread_mutex_unlock y termina en pthread_mutex_lock. Medios: se accedió a la misma ubicación de memoria cuando su mutex no estaba bloqueado, y ese hilo estaba fuera de sus dos funciones.

Ahora, un vistazo a la información de la ubicación de memoria en conflicto:

Allocation context: BSS section of /usr/lib/libstdc++.so.6.0.8 

El BSS significa que es una variable global/estática. Y está definido en algún lugar dentro de libstdc.

Conclusión:

Esta condición de carrera no tiene nada que ver con las estructuras de datos. Está relacionado con STL. Un hilo realiza algo en un std::string (lo asigna a una cadena vacía para ser exacto), mientras que el otro hilo probablemente también hace algo relacionado con STL.

Por cierto Recuerdo que hace varios años que he escrito una aplicación multi-hilo, y había problemas con std::string allí. Como descubrí, la implementación de STL (que era un Dunkimware) implementó la cadena como referencia contada, mientras que el recuento de referencias fue no thread-safe.

Tal vez esto es lo que le sucede a usted también? ¿Quizás debería establecer alguna bandera/opción de compilación al construir una aplicación de subprocesos múltiples?

+0

valdo, gracias por la actualización. Sí, creo que es el mismo problema exacto. cuando cambié todo a cadenas estilo C, los errores desaparecieron. – Nik

+0

Sin embargo, es una publicación útil. Ahora creo que para probar mis aplicaciones con Valgrind, utilizo multi-threading en gran medida. Acerca de las cadenas: Usted dice que el problema desaparece cuando cambia a "cadenas tipo C". ¿Pero a qué llamas las "cadenas de estilo C"? Más precisamente, ¿cuál es su política sobre su manipulación y vida? Creo que el problema está relacionado con el hecho de que sus cadenas cuenten o no cuenten con referencias. Medios: cuando asigna una cadena a otra: ¿realmente crea una copia de su cadena, o asigna otra idéntica a esa? Y si se trata de referencias, ¿es seguro para subprocesos? – valdo

+0

Se supone que STL es seguro para subprocesos en el sentido de que no es un problema usarlos con hilo si se bloquea correctamente o si solo realiza lecturas de varios hilos – Nikko

0

Nevermind. Soy un idiota y me las arreglé para olvidar que las cadenas de C++ son mutables. Cambié el código para usar cadenas de estilo c y mis problemas de condición de carrera desaparecieron.

Dejando a un lado para cualquier persona que lea esta publicación, ¿alguien sabe una buena biblioteca de cadenas inmutables para C++? Pensé que el impulso tenía uno, pero no he podido encontrar nada concluyente al respecto.

Gracias.

+0

He trabajado con threads y std :: strings (o STL containsres ..) sin ningún problema si todo está correctamente protegido. ¿Qué compilador/STL usas? – Nikko

+0

Con gcc y linux nunca tuve problemas ... – Nikko

+0

es un problema conocido con las cadenas stl de C++. Como utilizan el recuento de referencias y son mutables, este problema parece surgir con bastante regularidad. usando gcc 4.1 en Centos 5 – Nik

0

STL se supone que es seguro para subprocesos en el sentido de que no es un problema para usarlos con hilo si se bloquea correctamente o si simplemente realizar multi-hilo lee

hubo sorpresas! Sí, es supone, pero deja que te diga una tienda sobre lo que realmente sucedió .

que tenían una aplicación multit-hreaded. Había una estructura de datos con cadenas (std::string). Estaba bloqueado con una sección crítica.

Otros objetos con el tiempo necesario para tomar las cuerdas a partir de ahí. Hicieron una copia de esas cadenas de la siguiente manera:

// Take a string 
std::string str; 
{ 
    Autolock l(g_CritSect); 
    str = g_SomeStr; 
} 

Se utilizó la misma estrategia para ajustar esas cadenas:

// Put a string 
std::string str; 
{ 
    Autolock l(g_CritSect); 
    g_SomeStr = str; 
} 

Y, ¿adivinen qué? ¡Crash!

¿Pero por qué? Porque la declaración de asignación de la cadena realmente no produce una copia del bloque de memoria que contiene la cadena. En cambio, el mismo bloque de memoria se reutiliza (hace referencia).

Bueno, esto no es necesariamente malo. Pero lo malo fue que std :: string implementó el recuento de referencias de las cadenas no de una manera segura para subprocesos. Usó aritmética regular ++ y - en lugar de InterlockedIncrement y etc.

Lo que sucedió es que el objeto str hace referencia a la misma cadena. Y luego lo desclasifica en su destructor (o cuando se asigna explícitamente a otra cadena). Y esto sucede fuera de de la región bloqueada.

Así que esas cadenas no se pueden utilizar en una aplicación multi-hilo. Y es casi imposible implementar el bloqueo correcto para solucionar este problema, ya que los datos reales a los que se hace referencia pasan silenciosamente de un objeto a otro.

Y que la implementación de de STL se declaró segura para subprocesos.

+0

Es de suponer que podría forzar a la copia a copiar realmente, p. g_SomeStr = str.c_str()? [Obviamente, es un trabajo temporal horrible, y es mejor cambiar a un STL no borked.] –

+0

Sí, siempre hay una mala implementación, pero no siempre es así – Nikko

2

Este informe se puede ignorar de forma segura. Se desencadena por la forma en que se implementa std :: string en libstdC++. Este problema ha sido resuelto en la versión libstdC++ incluida con gcc 4.4.4 o posterior. Vea también GCC bugzilla item #40518 para los detalles.

Cuestiones relacionadas