2009-11-02 19 views
31

Teniendo en cuenta que:std :: string en un programa multi-hilo

1) El estándar de C++ 03 no se refiere a la existencia de hilos de ninguna manera

2) El estándar de C++ 03 deja a las implementaciones de decidir si std::string debe utilizar la semántica Copy-on-Write en su constructor copia

3) Copy-on-Write semántica a menudo conducen a un comportamiento impredecible en un programa multi-hilo

I llegar a lo siguiente, aparentemente controvertido al, conclusión:

Simplemente no se puede utilizar con seguridad y de forma portátil std :: string en un programa multi-hilo

Obviamente, hay una estructura de datos STL es seguro para subprocesos. Pero al menos, con std :: vector, por ejemplo, puede simplemente usar mutexes para proteger el acceso al vector. Con una implementación de std :: string que utiliza COW, ni siquiera se puede hacer de manera confiable sin editar la semántica de recuento de referencias en profundidad dentro de la implementación del proveedor.

ejemplo del mundo real:

En mi empresa, tenemos una aplicación multi-hilo que ha sido minuciosamente la unidad a prueba y ejecución de Valgrind innumerables veces. La aplicación se ejecutó durante meses sin problemas de ningún tipo. Un día, recompilo la aplicación en otra versión de gcc, y de repente obtengo segfaults al azar todo el tiempo. Valgrind ahora informa sobre accesos de memoria no válidos en profundidad dentro de libstdC++, en el constructor de copia std :: string.

¿Cuál es la solución? Bueno, por supuesto, podría typedef std::vector<char> como una clase de cadena, pero en realidad, eso es una mierda. También podría esperar C++ 0x, y pido que los implementadores renuncien a COW. O, (estremecimiento), podría usar una clase de cuerda personalizada. Personalmente siempre enfurezco a los desarrolladores que implementan sus propias clases cuando una biblioteca preexistente funciona bien, pero, sinceramente, necesito una clase de cadena de caracteres que estoy seguro de que no está utilizando la semántica de COW; y std :: string simplemente no garantiza eso.

¿Estoy en lo cierto que std::string simplemente no se puede utilizar de manera confiable en absoluto en programas portátiles, de múltiples hilos? ¿Y cuál es una buena solución?

+1

ver http://stackoverflow.com/questions/1594803/is-stdstring-thead-safe-with-gcc-4-3 – sellibitze

+0

Guau, ni siquiera sabía que la implementación de la cadena VACA todavía está por ahí. – sbi

+0

Si su implementación de STL utiliza un código no seguro para subprocesos, debe reemplazarlo por uno mejor. Esto parece un error para mí. – rpg

Respuesta

4

Dado que la norma no dice una palabra acerca de los modelos de memoria y es completamente inconsciente thread, yo diría que definitivamente no se puede asumir cada aplicación será no vaca así que no, no se puede

Aparte de eso, si conoce sus herramientas, la mayoría de las implementaciones usarán cadenas que no sean cow para permitir el multi-threading.

+13

"la mayoría de las implementaciones usarán cadenas que no sean cow para permitir multi-threading". No del todo cierto, de hecho ** la mayoría de los ** compiladores de C++ implementan cadenas COW: gcc, intel, HP ... MSVC no lo hace. La mayoría de las implementaciones (con la excepción de MSVC6) son seguras para hilos porque están usando contadores atómicos. – Artyom

0

que regulan el acceso cadena:

  • hacen std::string miembros privados
  • retorno const std::string& de captadores
  • emisores de modificar el miembro

Esto siempre ha funcionado bien para mí y es correcta ocultación de datos.

+0

¿El retorno de una garantía de referencia constante es correcto? Anteriormente pensé que sí, pero recientemente me sorprendieron algunos desagradables problemas de concurrencia, por ejemplo: const std :: string & MyClass :: GetString() const { Bloqueo MutexLock (m_Mutex); return m_MyString; } en este caso, ¿no se desbloquea el mutex * antes * del constructor de copia del valor devuelto? En ese caso, es probable que tenga condiciones de carrera. –

+0

se disculpa por el formato ... –

+0

@the_mandrill: "no se desbloquea el mutex antes del constructor de copia del valor de retorno" Por supuesto, se desbloquea antes de eso. Todo lo que proteges es la creación de una referencia. Devuelva por copia en este escenario. – sbi

8

Tienes razón. Esto se solucionará en C++ 0x.Por ahora, debe confiar en la documentación de su implementación. Por ejemplo, versiones libstdC++ recientes (GCC) le permiten usar objetos de cadena como si ningún objeto de cadena comparte su búfer con otro. C++ 0x obliga a la implementación de una biblioteca a proteger al usuario del "intercambio oculto".

0

Si desea desactivar la semántica vaca, que podría forzar sus cuerdas para hacer copias:

// instead of: 
string newString = oldString; 

// do this: 
string newString = oldString.c_str(); 

Como se ha señalado, sobre todo si se podría haber encajado nulos, entonces usted debe utilizar el ctor iterador:

string newString(oldString.begin(), oldString.end()); 
+2

Parece más fácil y mejor (especialmente para la calidad del código) simplemente usar otra implementación. – sbi

+2

El código anterior llama a 'strlen()' para calcular la longitud ya conocida. Mejor 'string newString (oldString.begin(), oldString.end())'. –

+1

@Bill: esto también se rompe si la cadena contiene caracteres nulos incrustados (lo cual es perfectamente legal para las derivadas 'std :: basic_string <>') – sehe

11

No puede hacer nada de forma segura y portátil en un programa de subprocesos múltiples. No hay tal cosa como un programa portable C++ de subprocesos múltiples, precisamente porque los hilos arrojan todo lo que dice C++ sobre el orden de las operaciones y los resultados de modificar cualquier variable, fuera de la ventana.

Tampoco hay nada en el estándar que garantice que vector se pueda usar de la manera que usted dice. Sería legal proporcionar una implementación de C++ con una extensión de subprocesamiento en la que, por ejemplo, cualquier uso de un vector fuera del subproceso en el que se inicializó da como resultado un comportamiento indefinido. En el instante en que inicia un segundo hilo, ya no usa C++ estándar, y debe buscar en el proveedor del compilador lo que es seguro y lo que no.

Si su proveedor proporciona una extensión de subprocesamiento, y también proporciona una cadena de caracteres estándar con COW que (por lo tanto) no se puede hacer segura para subprocesos, entonces creo que por el momento su argumento es con su proveedor o con el extensión de subprocesamiento, no con el estándar C++. Por ejemplo, podría decirse que POSIX debería haber excluido cadenas COW en programas que usan pthreads.

Puede hacer que sea seguro tener un único mutex, que se toma al hacer cualquier mutación de cadena, y cualquier lectura de una cadena que sea el resultado de una copia. Pero probablemente tendrías una contienda paralizante en ese mutex.

2

Puede usar STLport. Proporciona cadenas que no son COW. Y tiene el mismo comportamiento en diferentes plataformas.

Este article presenta la comparación de cadenas STL con el copia-en-escritura y noncopy- argorithms sobre-escritura, basado en cadenas, cuerdas y STLport GNU libstdC++ implementaciones.

En una empresa donde trabajo tengo cierta experiencia ejecutando la misma aplicación de servidor construida con STLport y sin STLport en HP-UX 11.31. La aplicación fue compilada con gcc 4.3.1 con nivel de optimización O2. Así que cuando ejecuto el programa construido con STLport procesa las solicitudes un 25% más rápido en comparación con el mismo programa construido sin STLport (que usa la propia biblioteca STL de gcc).

Hice un perfil de ambas versiones y descubrí que la versión sin STLport pasa mucho más tiempo en pthread_mutex_unlock() (2.5%) en comparación con la versión con STLport (1%). Y el propio pthread_mutex_unlock() en la versión sin STLport se llama desde una de las funciones std :: string.

Sin embargo, cuando después del perfilado he cambiado las asignaciones de cadenas en más a menudo llamadas funciones de este modo:

string_var = string_var.c_str(); // added .c_str() 

hubo una mejora significativa en el rendimiento de la versión sin STLport.

0

En MSVC, std :: string ya no es un puntero compartido contado de referencia en un contenedor. Eligen el valor por valor de todos los contenidos en cada constructor de copias y operador de asignación, para evitar problemas de subprocesamiento múltiple.

3

Una forma más correcta de verlo sería "No puede usar C++ de forma segura y portátil en un entorno multiproceso". No hay garantía de que otras estructuras de datos se comporten con sensatez tampoco. O que el tiempo de ejecución no hará estallar tu computadora. La norma no garantiza nada sobre hilos.

Para hacer cualquier cosa con hilos en C++, tiene que confiar en las garantías definidas por la implementación. Y luego puede usar de manera segura std::string porque cada implementación le dice si es seguro usarlo en un entorno enhebrado.

Perdió la esperanza de la verdadera portabilidad en el momento en que generó un segundo hilo. std::string no es "menos portátil" que el resto del idioma/biblioteca.

+0

Tienes razón, por supuesto. Si hablamos de lo que garantiza el estándar, ningún programa C++ de subprocesos múltiples es portátil.Sin embargo, el estado de cosas * de facto es que puede escribir código de subprocesos múltiples que se comportará de manera consistente en la mayoría de las plataformas modernas usando cosas como pthreads o hilos de refuerzo, con la notable excepción del código que usa 'std :: string', debido a la posibilidad no despreciable de semántica de VACA. O para decirlo de otra manera: el código multiproceso que usa 'std :: vector' no es probable que se rompa * en ningún lado * en estos días; pero no se puede decir lo mismo de 'std :: string'. –