c++ multithreading c++11 memory-model

C++ 11 memory_order_acquire y memory_order_release semantics?



multithreading c++11 (1)

http://en.cppreference.com/w/cpp/atomic/memory_order , y otras referencias en línea de C ++ 11, definen memory_order_acquire y memory_order_release como:

  • Operación de adquisición: no se pueden reordenar las lecturas en el hilo actual antes de esta carga.
  • Operación de liberación: no se pueden reordenar escrituras en el hilo actual después de este almacenamiento.

Esto parece permitir que las escrituras posteriores a la adquisición se ejecuten antes de la operación de adquisición, lo que también me parece extraño (la semántica habitual de las operaciones de adquisición / liberación restringe el movimiento de todas las operaciones de memoria).

La misma fuente en línea ( http://en.cppreference.com/w/cpp/atomic/atomic_flag ) sugiere que se puede construir un mutex de spinlock usando C ++ atomics y las reglas de ordenamiento de memoria relajada mencionadas anteriormente:

lock mutex: while (lock.test_and_set(std::memory_order_acquire)) unlock mutex: lock.clear(std::memory_order_release);

Con esta definición de bloqueo / desbloqueo, ¿no se rompería el código simple a continuación si memory_order_acquire / release se define de esta manera (es decir, no se prohíbe la reordenación de escrituras posteriores a la adquisición)?

Thread1: (0) lock (1) x = 1; (2) if (x != 1) PANIC (3) unlock Thread2: (4) lock (5) x = 0; (6) unlock

¿Es posible la siguiente ejecución: (0) bloqueo, (1) x = 1, (5) x = 0, (2) PÁNICO? ¿Qué me perdí?


La implementación de mutex de spinlock me parece bien. Creo que tienen las definiciones de adquirir y liberar completamente equivocadas.

Aquí está la explicación más clara de los modelos de consistencia de adquisición / lanzamiento que conozco: Gharachorloo; Lenoski; Laudon; Gibbons; Gupta; Hennessy: consistencia de memoria y orden de eventos en multiprocesadores de memoria compartida escalables, Int''l Symp Comp Arch , ISCA (17): 15-26, 1990, doi 10.1145 / 325096.325102 . (El doi está detrás del muro de pago de la ACM. El enlace real es a una copia que no está detrás del muro de pago).

Vea la Condición 3.1 en la Sección 3.3 y la Figura 3 adjunta:

  • antes de que se permita que una carga ordinaria o acceso a la tienda funcione con respecto a cualquier otro procesador, se deben realizar todos los accesos adquiridos anteriores, y
  • antes de que se permita que un acceso de liberación funcione con respecto a cualquier otro procesador, se deben realizar todos los accesos de almacenamiento y carga ordinarios anteriores, y
  • Los accesos especiales son [secuencialmente] consistentes entre sí.

El punto es este: las adquisiciones y los lanzamientos son secuencialmente consistentes (todos los subprocesos coinciden globalmente en el orden en el que se obtuvieron y los liberados). Todos los subprocesos coinciden globalmente en que las cosas que suceden entre una adquisición y una liberación en un hilo específico ocurrieron entre y liberar. Pero las cargas y los almacenes normales después de un lanzamiento se pueden mover (ya sea por hardware o por el compilador) sobre el lanzamiento, y los cargamentos y las cargas normales antes de que se permita mover una adquisición (ya sea por hardware o por el compilador) después de la adquisición .

En la norma C ++ (usé el enlace al borrador de enero de 2012), la sección correspondiente es 1.10 (páginas 11 a 14).

La definición de " sucede antes" está diseñada para ser modelada después de Lamport; Tiempo, relojes y ordenación de eventos en un sistema distribuido, CACM , 21 (7): 558-565, julio de 1978 . Las adquisiciones de C ++ corresponden a las recepciones de Lamport, las liberaciones de C ++ corresponden a los envíos de Lamport. Lamport colocó un orden total en la secuencia de eventos dentro de un solo hilo, donde C ++ debe permitir un orden parcial (consulte la Sección 1.9, Párrafos 13-15, página 10 para la definición de C ++ de secuencia antes ). Aún así, la secuencia- Antes de ordenar es más o menos lo que esperas. Las declaraciones se secuencian en el orden en que se dan en el programa. Sección 1.9, párrafo 14: "Cada cálculo de valor y efecto de lado asociado con una expresión completa se secuencia antes de cada cálculo de valor y efecto de lado asociado con la siguiente expresión completa a evaluar".

El punto principal de la Sección 1.10 es decir que un programa que está libre de datos genera el mismo valor bien definido como si el programa se ejecutara en una máquina con una memoria coherente de forma secuencial y sin reordenamiento del compilador. Si hay una carrera de datos, entonces el programa no tiene semántica definida. Si no hay una carrera de datos, se permite al compilador (o máquina) reordenar las operaciones que no contribuyen a la ilusión de consistencia secuencial.

La sección 1.10, párrafo 21 (página 14) dice: Un programa no está libre de datos si hay un par de accesos A y B desde diferentes hilos al objeto X, al menos uno de esos accesos tiene un efecto secundario, y ninguno A sucede-antes de B, ni B sucede-antes de A. De lo contrario, el programa está libre de datos.

Los párrafos 6-20 dan una definición muy cuidadosa de la relación de suceso antes. La definición clave es el párrafo 12:

"Una evaluación A sucede antes de una evaluación B si:

  • A se secuencia antes que B, o
  • Un inter-thread sucede antes de B. "

Por lo tanto, si una adquisición se secuencia antes (en el mismo hilo) en casi cualquier otra declaración, entonces la adquisición debe aparecer antes de esa declaración. (Incluyendo si esa declaración realiza una escritura.)

Del mismo modo: si casi cualquier declaración se secuencia antes (en el mismo hilo) de una versión, entonces esa declaración debe aparecer antes de la versión. (Incluyendo si esa declaración solo hace un cálculo de valor (lectura).)

La razón por la que al compilador se le permite mover otros cálculos desde después de una versión hasta antes de una versión (o desde antes de una adquisición hasta después de una adquisición) es debido al hecho de que esas operaciones específicamente no tienen un inter-thread antes de la relación ( porque están fuera de la sección crítica). Si compiten, la semántica no está definida, y si no compiten (porque no se comparten), no se puede saber exactamente cuándo ocurrieron con respecto a la sincronización.

Lo que es una manera muy larga de decir: las definiciones de adquisición y liberación de cppreference.com están totalmente equivocadas. Su programa de ejemplo no tiene una condición de carrera de datos, y PANIC no puede ocurrir.