No es el compilador y el sistema operativo específico, es específico de la arquitectura. El compilador y el sistema operativo entran en juego porque son las herramientas con las que trabajas, pero no son ellos los que establecen las reglas reales. Esta es la razón por la cual el estándar de C++ no tocará el problema.
Nunca en mi vida he oído hablar de una escritura de enteros de 64 bits, que se puede dividir en dos escrituras de 32 bits, siendo interrumpida a la mitad. (Sí, eso es una invitación para que otros publiquen contraejemplos.) Específicamente, nunca he oído hablar de la unidad de almacenamiento/carga de una CPU que permite que se interrumpa una escritura desalineada; una fuente de interrupción tiene que esperar a que se complete todo el acceso desalineado.
Para tener una unidad de carga/almacenamiento interrumpible, su estado debería guardarse en la pila ... y la unidad de carga/almacenamiento es lo que guarda el resto del estado de la CPU en la pila. Esto sería enormemente complicado, y propenso a errores, si la unidad de carga/almacenamiento fuera interrumpible ... y todo lo que obtendría es un ciclo menos latencia al responder a las interrupciones, que, en el mejor de los casos, se mide en decenas de ciclos. Totalmente no vale la pena.
En 1997, un compañero de trabajo y yo escribimos una plantilla de cola C++ que se utilizó en un sistema de multiprocesamiento. (Cada procesador tenía su propio sistema operativo en ejecución y su propia memoria local, por lo que estas colas solo eran necesarias para la memoria compartida entre procesadores). Buscamos una forma de hacer que la cola cambiara de estado con una sola escritura de enteros, y tratamos esta escritura como una operación atómica. Además, requerimos que cada extremo de la cola (es decir, el índice de lectura o escritura) sea propiedad de un solo procesador. Trece años más tarde, el código sigue funcionando bien, e incluso tenemos una versión que maneja múltiples lectores.
Aún así, si desea tratar un entero de 64 bits, escriba como atómico, alinee el campo con un límite de 64 bits. ¿Por que preocuparse?
EDITAR: Para el caso que mencionas en tu comentario, necesitaría más información para estar seguro, así que déjame dar un ejemplo de algo que podría implementarse sin un código de sincronización especializado.
Supongamos que tiene N escritores y un lector. Desea que los escritores puedan señalar eventos al lector. Los eventos en sí mismos no tienen datos; solo quieres contar un evento, realmente.
Declarar una estructura para la memoria compartida, compartida entre todos los escritores y el lector: (. Establecer como una clase o una plantilla o lo que sea que le parezca)
#include <stdint.h>
struct FlagTable
{ uint32_t flag[NWriters];
};
Cada escritor necesita se le dijo que su índice y se le dio un puntero a esta tabla:
class Writer
{public:
Writer(FlagTable* flags_, size_t index_): flags(flags_), index(index_) {}
void SignalEvent(uint32_t eventCount = 1);
private:
FlagTable* flags;
size_t index;
}
Cuando el escritor quiere indicar un evento (o varios), se actualiza su bandera:
void Writer::SignalEvent(uint32_t eventCount)
{ // Effectively atomic: only one writer modifies this value, and
// the state changes when the incremented value is written out.
flags->flag[index] += eventCount;
}
El lector mantiene una copia local de todos los valores de los indicadores se ha visto:
class Reader
{public:
Reader(FlagTable* flags_): flags(flags_)
{ for(size_t i = 0; i < NWriters; ++i)
seenFlags[i] = flags->flag[i];
}
bool AnyEvents(void);
uint32_t CountEvents(int writerIndex);
private:
FlagTable* flags;
uint32_t seenFlags[NWriters];
}
Para averiguar si han ocurrido eventos, sólo se ve por los valores modificados:
bool Reader::AnyEvents(void)
{ for(size_t i = 0; i < NWriters; ++i)
if(seenFlags[i] != flags->flag[i])
return true;
return false;
}
Si algo sucedió, podemos comprobar cada fuente y obtener el recuento de eventos:
uint32_t Reader::CountEvents(int writerIndex)
{ // Only read a flag once per function call. If you read it twice,
// it may change between reads and then funny stuff happens.
uint32_t newFlag = flags->flag[i];
// Our local copy, though, we can mess with all we want since there
// is only one reader.
uint32_t oldFlag = seenFlags[i];
// Next line atomically changes Reader state, marking the events as counted.
seenFlags[i] = newFlag;
return newFlag - oldFlag;
}
¿Ahora el gran problema en todo esto? Es no bloqueante, lo que quiere decir que no puedes hacer que el Reader duerma hasta que un escritor escriba algo. El lector tiene que elegir entre sentarse en un spin-loop esperando AnyEvents()
para devolver true
, lo que minimiza la latencia, o puede dormir un poco cada vez, lo que ahorra CPU pero puede permitir que se acumulen muchos eventos. Entonces es mejor que nada, pero no es la solución para todo.
Utilizando primitivas de sincronización reales, bastaría con envolver este código con un mutex y una variable de condición para bloquearlo correctamente: el Reader dormiría hasta que hubiera algo que hacer. Como usó operaciones atómicas con las banderas, podría mantener la cantidad de tiempo que el mutex está bloqueado al mínimo: el escritor solo necesitaría bloquear el mutex el tiempo suficiente para enviar la condición, y no establecer el indicador, y el lector solo necesita esperar la condición antes de llamar al AnyEvents()
(básicamente, es como el caso del bucle de suspensión anterior, pero con una espera de condición en lugar de una llamada de espera).
, operación atómica puede ser posible, http://stackoverflow.com/questions/930897/c-atomic-operations-for-lock-free -las estructuras pueden ayudar. –