bool c++ multithreading c++11 stl atomic

atomic bool c++



¿Por qué std:: atomic initialisation no realiza la liberación atómica para que otros subprocesos puedan ver el valor inicializado? (3)

Algo muy extraño apareció durante el proceso de desinfección del impulso propuesto :: concurrent_unordered_map y se cuenta en esta publicación del blog . En resumen, bucket_type se ve así:

struct bucket_type_impl { spinlock<unsigned char> lock; // = 2 if you need to reload the bucket list atomic<unsigned> count; // count is used items in there std::vector<item_type, item_type_allocator> items; bucket_type_impl() : count(0), items(0) { } ...

Sin embargo, el desinfectante de hilos afirma que hay una carrera entre la construcción de un tipo de cubo y su primer uso, específicamente cuando se carga el conteo atómico. Resulta que si inicializa un std :: atomic <> a través de su constructor, esa inicialización no es atómica y, por lo tanto, la ubicación de la memoria no se libera atómicamente y, por lo tanto, no es visible para otros subprocesos, lo cual es contrario a la intuición, dado que es atómico. la mayoría de las operaciones atómicas por defecto son memory_order_seq_cst. Por lo tanto, debe hacer explícitamente un almacén de versiones después de la construcción para inicializar el atómico con un valor visible para otros subprocesos.

¿Hay alguna razón extremadamente apremiante por la cual std :: atomic con un constructor que consume valor no se inicializa con la semántica de lanzamiento? Si no, creo que esto es un defecto de la biblioteca.

Edición: la respuesta de Jonathan es mejor para la historia en cuanto a por qué, pero la respuesta de ecatmur se relaciona con el informe de defectos de Alastair sobre el asunto, y cómo se cerró simplemente agregando una nota para decir que la construcción no ofrece visibilidad a otros hilos. Por lo tanto, voy a conceder la respuesta a ecatmur. Gracias a todos los que respondieron, creo que el camino está claro para solicitar un constructor adicional, al menos se destacará en la documentación de que hay algo inusual en el constructor que consume valor.

Edición 2: terminé planteando esto como un defecto en el lenguaje C ++ con el comité, y Hans Boehm, quien preside la parte de Concurrencia, considera que esto no es un problema por las siguientes razones:

  1. Ningún compilador actual de C ++ en 2014 trata el consumo como diferente a adquirir. Como nunca, en el código del mundo real, pasará un atómico a otro subproceso sin pasar por alguna liberación / adquisición, la inicialización del atómico se haría visible a todos los subprocesos que usan el atómico. Creo que esto está bien hasta que los compiladores se pongan al día, y antes de eso el Thread Sanitiser advertirá sobre esto.

  2. Si estás haciendo un emparejamiento diferente de consumo-adquisición-liberación como yo (estoy usando un atómico liberación-dentro-bloqueo / consumo-fuera-bloqueo para evitar especulativamente un lanzamiento-adquisición spinlock donde era innecesario) entonces eres un gran Bastante chico para saber que debe almacenar manualmente los atómicos de liberación después de la construcción. Ese es probablemente un punto justo.


Diría que es porque una construcción nunca es una operación de comunicación de subprocesos: cuando construyes un objeto, llenas la memoria no inicializada con valores sensibles. No hay forma de que otro subproceso diga si esa operación ha finalizado a menos que se comunique explícitamente por el subproceso de construcción . Si compites con la construcción de todos modos, inmediatamente tienes un comportamiento indefinido.

Dado que el subproceso creador debe publicar explícitamente su éxito en la construcción de un valor antes de que otro subproceso pueda ser utilizado, simplemente no tiene sentido sincronizar los constructores.


Es porque el constructor de conversión es constexpr , y constexpr funciones constexpr no pueden tener efectos secundarios, como la semántica atómica.

En DR846 , Alastair Meredith escribe:

No estoy seguro de si la inicialización está implícita mediante el uso de la palabra clave constexpr (que restringe la forma de un constructor), pero incluso si ese es el caso, creo que vale la pena explicarlo explícitamente, ya que la inferencia sería demasiado sutil. caso.

La resolución para ese defecto (por Lawrence Crowl) fue documentar al constructor con la nota:

[ Nota: La construcción no es atómica. "Nota final "

La nota se amplió a la redacción actual, dando un ejemplo de una posible carrera de memoria (a través de memory_order_relaxed operaciones memory_order_relaxed que comunican la dirección del atómico) en DR1478 .

La razón por la que el constructor de conversión debe ser constexpr es (principalmente) para permitir la inicialización estática. En DR768 vemos:

Más discusión: ¿por qué el ctor está etiquetado como "constexpr"? Lawrence [Crowl] dijo que esto permite que el objeto se inicialice estáticamente, y eso es importante porque de lo contrario habría una condición de carrera en la inicialización.

Por lo tanto: hacer que el constructor constexpr elimine las condiciones de carrera en los objetos de vida estática, al costo de una carrera en objetos de vida dinámica que solo ocurre en situaciones bastante artificiales, ya que para una carrera se produce la ubicación de memoria del objeto atómico de vida dinámica debe comunicarse a otro hilo de una manera que no resulte en que el valor del objeto atómico también se sincronice con ese hilo.


Esa es una elección de diseño intencional (incluso hay una nota en la advertencia estándar al respecto) y creo que se hizo en un intento de ser compatible con C.

Los componentes atómicos de C ++ 11 se diseñaron de modo que WG14 para C también pudieran utilizarlos, utilizando las funciones no miembros como atomic_load con tipos como atomic_int lugar de las funciones miembro de C ++, solo std::atomic<int> . En el diseño original, el tipo atomic_int no tiene propiedades especiales y la atomicidad solo se logra a través de atomic_load() y otras funciones. En ese modelo, atomic_init no es una operación atómica, simplemente inicializa un POD. Solo una atomic_store(&i, 1) subsiguiente de atomic_store(&i, 1) sería atómica.

Al final, WG14 decidió hacer las cosas de manera diferente, agregando el especificador _Atomic que hace que el tipo atomic_int tenga propiedades mágicas. No estoy seguro de si eso significa que la inicialización de C atómica podría ser atómica (tal como está, atomic_init en C11 y C ++ 11 está documentada como no atómica), por lo que tal vez la regla de C ++ 11 sea innecesaria. Sospecho que la gente argumentará que hay una buena razón de rendimiento para mantener la inicialización no atómica, como dice el comentario de interjay anterior, debe enviar una notificación al otro hilo de que el objeto está construido y listo para ser leído, por lo que la notificación podría Introducir las vallas necesarias. Hacerlo una vez para la inicialización std::atomic y luego una segunda vez para decir que el objeto está construido podría ser un desperdicio.