Atomicidad en C ++: mito o realidad

He estado leyendo un artículo sobre Lockless Programming en MSDN. Dice :

En todos los procesadores modernos, puede suponer que las lecturas y escrituras de tipos nativos alineados naturalmente son atómicas . Siempre que el bus de memoria sea al menos tan ancho como el tipo que se lee o escribe, la CPU lee y escribe estos tipos en una sola transacción de bus, lo que hace que otros hilos no puedan verlos en un estado medio completo.

Y da algunos ejemplos:

// This write is not atomic because it is not natively aligned. DWORD* pData = (DWORD*)(pChar + 1); *pData = 0; // This is not atomic because it is three separate operations. ++g_globalCounter; // This write is atomic. g_alignedGlobal = 0; // This read is atomic. DWORD local = g_alignedGlobal; 

Leí muchas respuestas y comentarios que decían: no hay nada garantizado que sea atómico en C ++ y ni siquiera se menciona en standarts, en SO y ahora estoy un poco confundido. ¿Estoy malinterpretando el artículo? ¿O el autor del artículo habla sobre cosas que no son estándar y específicas para el comstackdor MSVC ++?

Entonces, según el artículo, las asignaciones siguientes deben ser atómicas, ¿no?

 struct Data { char ID; char pad1[3]; short Number; char pad2[2]; char Name[5]; char pad3[3]; int Number2; double Value; } DataVal; DataVal.ID = 0; DataVal.Number = 1000; DataVal.Number2 = 0xFFFFFF; DataVal.Value = 1.2; 

Si es verdadero, reemplaza Name[5] y pad3[3] con std::string Name; hacer alguna diferencia en la alineación de la memoria? ¿Las asignaciones a las variables Number2 y Value seguirán siendo atómicas?

¿Alguien puede explicar?

Esta recomendación es específica de la architecture. Es cierto para x86 y x86_64 (en una progtwigción de bajo nivel). También debe verificar que el comstackdor no reordena su código. Puede usar “barrera de memoria del comstackdor” para eso.

Las lecturas y escrituras atómicas de bajo nivel para x86 se describen en los manuales de referencia de Intel, “Manual de desarrollo de software de architectures Intel® 64 e IA-32”, Volumen 3A ( http://www.intel.com/Assets/PDF/manual/253668. pdf ), sección 8.1.1

8.1.1 Operaciones atómicas garantizadas

El procesador Intel486 (y los procesadores más nuevos desde entonces) garantiza que las siguientes operaciones básicas de memoria siempre se llevarán a cabo atómicamente:

  • Leer o escribir un byte
  • Leer o escribir una palabra alineada en un límite de 16 bits
  • Leer o escribir una doble palabra alineada en un límite de 32 bits

El procesador Pentium (y los procesadores más nuevos desde entonces) garantiza que las siguientes operaciones adicionales de memoria siempre se llevarán a cabo atómicamente:

  • Leer o escribir una quadword alineada en un límite de 64 bits
  • Accesos de 16 bits a ubicaciones de memoria no guardadas en caché que se ajustan a un bus de datos de 32 bits

Los procesadores de familia P6 (y los procesadores más nuevos desde entonces) garantizan que la siguiente operación de memoria adicional siempre se llevará a cabo atómicamente:

  • Los accesos no alineados de 16, 32 y 64 bits a la memoria en caché que se ajustan a una línea de caché

Este documento también tiene más descripción de forma atómica para procesadores más nuevos como Core2. No todas las operaciones desalineadas serán atómicas.

Otro manual de inteligencia recomienda este documento:

http://software.intel.com/en-us/articles/developing-multithreaded-applications-a-platform-consistent-approach/

Creo que estás malinterpretando la cita.

La atomicidad se puede garantizar en una architecture determinada, usando instrucciones específicas (propias de esta architecture). El artículo de MSDN explica que se puede esperar que las lecturas y escrituras en los tipos incorporados de C ++ sean atómicas en la architecture x86 .

Sin embargo, el estándar de C ++ no presupone cuál es la architecture, por lo tanto, el estándar no puede hacer tales garantías. De hecho C ++ se utiliza en software integrado donde el soporte de hardware es mucho más limitado.

C ++ 0x define la clase de plantilla std::atomic , que permite convertir lecturas y escrituras en operaciones atómicas , cualquiera que sea el tipo. El comstackdor seleccionará la mejor forma de obtener la atomicidad en función de las características de tipo y la architecture orientada de manera estándar.

El nuevo estándar también define una gran cantidad de operaciones similares a MSVC InterlockExchange que también se comstackn con las primitivas disponibles más rápidas (pero seguras) que ofrece el hardware.

El estándar de C ++ no garantiza el comportamiento atómico. En la práctica, sin embargo, las operaciones simples de carga y almacenamiento serán atómicas, como dice el artículo.

Si necesita atomicidad, mejor sea explícito y use algún tipo de locking.

 *counter = 0; // this is atomic on most platforms *counter++; // this is NOT atomic on most platforms 

Tenga mucho cuidado al confiar en la atomicidad de las operaciones de tamaño de palabra simple porque las cosas pueden comportarse de manera diferente de lo que espera. En architectures multinúcleo, puede ser testigo de lecturas y escrituras desordenadas. Esto requerirá barreras de memoria para prevenir. (más detalles aquí ).

La conclusión para un desarrollador de aplicaciones es usar primitivas que el sistema operativo garantice sean atómicas o que usen lockings apropiados.

OMI, el artículo incorpora algunas suposiciones sobre la architecture subyacente. Como C ++ tiene solo algunos requisitos minimalistas en la architecture, no se pueden dar garantías, por ejemplo, sobre la atomicidad en el estándar. Por ejemplo, un byte tiene que tener al menos 8 bits, pero podría tener una architecture donde un byte es de 9 bits, pero un int 16 … teóricamente.

Entonces, cuando el comstackdor es específico para x86 architecutre, se pueden usar las características específicas.

NB: las estructuras generalmente están alineadas por defecto a un límite de palabras nativas. puede desactivarlo mediante sentencias #pragma, por lo que no se requieren los rellenos de relleno

Creo que lo que están tratando de entender, es que los tipos de datos implementados de forma nativa por el hardware, se actualizan dentro del hardware de modo que la lectura de otro hilo nunca le dará un valor “parcialmente” actualizado.

Considere un entero de 32 bits en una máquina de 32+ bits. Está escrito o leído completamente en 1 ciclo de instrucciones, mientras que los tipos de datos de tamaños más grandes, digamos 64 bits int en una máquina de 32 bits requerirán más ciclos, por lo que teóricamente el hilo que los escribe podría interrumpirse entre esos ciclos si el valor es no en un estado válido

No usar string no lo haría atómico, ya que string es una construcción de nivel superior y no se implementa en el hardware. Editar: según su comentario sobre lo que (didnt) significó para cambiar a cadena, no debería haber ninguna diferencia en los campos declarados después, como se menciona en otra respuesta, el comstackdor alineará los campos de forma predeterminada.

La razón por la que no está en el estándar es que, como se afirma en el artículo, se trata de cómo los procesadores modernos implementan las instrucciones. Su código C / C ++ estándar debería funcionar exactamente igual en una máquina de 16 o 64 bits (solo con diferencia de rendimiento), sin embargo, si supone que solo ejecutará en una máquina de 64 bits, entonces cualquier cosa de 64 bits o menos es atómica. (Tipo de SSE, etc.)

Creo que la atomicity tal como se menciona en el artículo tiene poco uso práctico. Esto significa que leerá / escribirá un valor válido, pero probablemente esté desactualizado. Entonces, al leer un int, lo leerá por completo, no a 2 bytes de un valor antiguo y otros 2 bytes de un nuevo valor que está siendo escrito actualmente por otro hilo.

Lo que es importante para la memoria compartida son las barreras de memoria. Y están garantizados por primitivas de sincronización como C ++ 0x tipos atomic , mutexes , etc.

No creo que cambie el char Name[5] por el de std::string Name marcará la diferencia si lo está utilizando solo para asignaciones de caracteres individuales , ya que el operador de índice devolverá una referencia directa al carácter subyacente. Una asignación de cadena completa no es atómica (y no puedes hacerlo con una matriz de caracteres, así que supongo que no pensabas usarla de esta manera).

    Intereting Posts