hilos ejercicios c++ c++11 thread-safety const c++-faq

ejercicios - ¿Const significa hilo seguro en C++ 11?



ejercicios de hilos en c (1)

Escuché que const significa thread-safe en C ++ 11 . ¿Es eso cierto?

Es algo cierto ...

Esto es lo que el Lenguaje estándar tiene que decir sobre seguridad de subprocesos:

[1.10 / 4] Dos evaluaciones de expresiones entran en conflicto si una de ellas modifica una ubicación de memoria (1.7) y la otra accede o modifica la misma ubicación de memoria.

[1.10 / 21] La ejecución de un programa contiene una carrera de datos si contiene dos acciones en conflicto en diferentes hilos, al menos uno de los cuales no es atómico, y ninguno sucede antes que el otro. Cualquier raza de datos de este tipo da como resultado un comportamiento indefinido.

que no es más que la condición suficiente para que ocurra una carrera de datos :

  1. Hay dos o más acciones que se realizan al mismo tiempo en una cosa determinada; y
  2. Al menos uno de ellos es una escritura.

La biblioteca estándar se basa en eso, yendo un poco más allá:

[17.6.5.9/1] Esta sección especifica los requisitos que las implementaciones deben cumplir para evitar carreras de datos (1.10). Cada función de biblioteca estándar deberá cumplir cada requisito a menos que se especifique lo contrario. Las implementaciones pueden evitar carreras de datos en casos distintos a los especificados a continuación.

[17.6.5.9/3] Una función de biblioteca estándar de C ++ no modificará directa o indirectamente objetos (1.10) accesibles por hilos que no sean el hilo actual a menos que se acceda a los objetos directa o indirectamente a través de los argumentos no const de la función, incluido this .

que en palabras simples dice que espera que las operaciones en objetos const sean seguras para hilos . Esto significa que la Biblioteca estándar no introducirá una carrera de datos siempre que las operaciones en objetos const de sus propios tipos

  1. Consiste completamente en lecturas, es decir, no hay escrituras; o
  2. Internamente sincroniza escrituras.

Si esta expectativa no es válida para uno de sus tipos, entonces usarla directa o indirectamente junto con cualquier componente de la Biblioteca estándar puede dar como resultado una carrera de datos . En conclusión, const significa seguro para subprocesos desde el punto de vista de la Biblioteca estándar . Es importante tener en cuenta que esto no es más que un contrato y el compilador no lo aplicará, si lo rompe obtendrá un comportamiento indefinido y estará solo. Si const está presente o no, no afectará la generación de código, al menos no con respecto a las carreras de datos .

¿ const significa que const ahora es el equivalente de Java synchronized ?

No De ningún modo...

Considere la siguiente clase simplificada que representa un rectángulo:

class rect { int width = 0, height = 0; public: /*...*/ void set_size( int new_width, int new_height ) { width = new_width; height = new_height; } int area() const { return width * height; } };

El area función de miembro es segura para subprocesos ; no porque sea const , sino porque está compuesto por operaciones de lectura. No hay escrituras involucradas, y al menos una escritura involucrada es necesaria para que ocurra una carrera de datos . Eso significa que puede llamar al area desde todos los hilos que desee y obtendrá los resultados correctos todo el tiempo.

Tenga en cuenta que esto no significa que rect es seguro para subprocesos . De hecho, es fácil ver cómo si una llamada al area ocurriera al mismo tiempo que una llamada a set_size en un rect dado, entonces el area podría terminar computando su resultado basado en un ancho antiguo y una nueva altura (o incluso en valores confusos).

Pero eso está bien, rect no es const así que no se espera que sea seguro para subprocesos después de todo. Un objeto declarado const rect , por otro lado, sería seguro para subprocesos, ya que no es posible const_cast (y si está considerando const_cast -ing algo originalmente declarado const entonces obtiene un comportamiento indefinido y eso es todo).

Entonces, ¿qué significa entonces?

Supongamos, por el bien de la argumentación, que las operaciones de multiplicación son extremadamente costosas y es mejor evitarlas cuando sea posible. Podríamos calcular el área solo si se solicita y luego guardarla en caché en caso de que se solicite nuevamente en el futuro:

class rect { int width = 0, height = 0; mutable int cached_area = 0; mutable bool cached_area_valid = true; public: /*...*/ void set_size( int new_width, int new_height ) { cached_area_valid = ( width == new_width && height == new_height ); width = new_width; height = new_height; } int area() const { if( !cached_area_valid ) { cached_area = width; cached_area *= height; cached_area_valid = true; } return cached_area; } };

[Si este ejemplo parece demasiado artificial, puede reemplazar mentalmente int por un entero muy grande dinámicamente asignado que es inherentemente no seguro para subprocesos y para el cual las multiplicaciones son extremadamente costosas.]

El area función de miembro ya no es seguro para subprocesos , realiza escrituras ahora y no está sincronizado internamente. ¿Es un problema? La llamada al area puede ocurrir como parte de un copia-constructor de otro objeto, dicho constructor podría haber sido llamado por alguna operación en un contenedor estándar , y en ese punto la biblioteca estándar espera que esta operación se comporte como una lectura en relación con los datos carreras ¡Pero estamos escribiendo!

Tan pronto como colocamos un rect en un contenedor estándar, directa o indirectamente, estamos celebrando un contrato con la Biblioteca estándar . Para seguir escribiendo en una función const mientras se sigue respetando ese contrato, necesitamos sincronizar internamente esas escrituras:

class rect { int width = 0, height = 0; mutable std::mutex cache_mutex; mutable int cached_area = 0; mutable bool cached_area_valid = true; public: /*...*/ void set_size( int new_width, int new_height ) { if( new_width != width || new_height != height ) { std::lock_guard< std::mutex > guard( cache_mutex ); cached_area_valid = false; } width = new_width; height = new_height; } int area() const { std::lock_guard< std::mutex > guard( cache_mutex ); if( !cached_area_valid ) { cached_area = width; cached_area *= height; cached_area_valid = true; } return cached_area; } };

Tenga en cuenta que hemos hecho que la función de area segura para subprocesos , pero la rect todavía no es segura para subprocesos . Una llamada al area ocurre al mismo tiempo que una llamada a set_size puede terminar computando el valor incorrecto, ya que las asignaciones de width y height no están protegidas por el mutex.

Si realmente quisiéramos un rect seguro para hilos, rect una primitiva de sincronización para proteger el rect seguro para hilos .

¿Se están quedando sin palabras clave ?

Sí lo son. Se han quedado sin palabras clave desde el primer día.

Fuente : Usted no sabe const y mutable - Herb Sutter

Escuché que const significa thread-safe en C ++ 11 . ¿Es eso cierto?

¿ const significa que const ahora es el equivalente de Java synchronized ?

¿Se están quedando sin palabras clave ?