¿Pueden las lecturas volátiles pero sin cercar producir valores indefinidamente obsoletos? (en hardware real)
En respuestaesta pregunta surgió una pregunta adicional sobre la situación del OP sobre la que no estaba seguro: se trata principalmente de una pregunta sobre la arquitectura del procesador, pero también con una pregunta en torno al modelo de memoria C ++ 11.
Básicamente, el código del OP estaba en bucle infinitamente en niveles de optimización más altos debido al siguiente código (ligeramente modificado para simplificar):
while (true) {
uint8_t ov = bits_; // bits_ is some "uint8_t" non-local variable
if (ov & MASK) {
continue;
}
if (ov == __sync_val_compare_and_swap(&bits_, ov, ov | MASK)) {
break;
}
}
dónde__sync_val_compare_and_swap()
es el CAS atómico de GCC incorporado. GCC (razonablemente) optimizó esto en un bucle infinito en el caso de quebits_ & mask
fue detectado para sertrue
antes de ingresar al bucle, omitir completamente la operación CAS, por lo que sugerí el siguiente cambio (que funciona):
while (true) {
uint8_t ov = bits_; // bits_ is some "uint8_t" non-local variable
if (ov & MASK) {
__sync_synchronize();
continue;
}
if (ov == __sync_val_compare_and_swap(&bits_, ov, ov | MASK)) {
break;
}
}
Después de responder, OP notó que el cambiobits_
avolatile uint8_t
Parece funcionar también. Sugerí no ir por ese camino, ya quevolatile
Normalmente no se debe usar para la sincronización, y de todos modos no parece haber un inconveniente en usar una cerca.
Sin embargo, lo pensé más, y en este caso la semántica es tal que realmente no importa si elov & MASK
la verificación se basa en un valor obsoleto, siempre que no se base en uno indefinidamente (es decir, siempre que el bucle se rompa eventualmente), ya que el intento real de actualizaciónbits_
está sincronizado. Asi esvolatile
suficiente aquí para garantizar que este bucle termina eventualmente sibits_
Se actualiza por otro hilo tal quebits_ & MASK == false
, para cualquier procesador existente? En otras palabras, en ausencia de una valla de memoria explícita, ¿es prácticamente posible que las lecturas no optimizadas por el compilador se optimicen efectivamente por el procesador en su lugar, de manera indefinida? (EDITAR: Para ser claro, estoy preguntando aquí qué podría hacer realmente el hardware moderno dado el supuesto de que el compilador emite lecturas en un bucle, por lo que técnicamente no es una pregunta de lenguaje, aunque es conveniente expresarlo en términos de semántica de C ++.
Ese es el ángulo del hardware, pero para actualizarlo un poco y convertirlo también en una pregunta sobre el modelo de memoria C ++ 11, considere la siguiente variación del código anterior:
// bits_ is "std::atomic<unsigned char>"
unsigned char ov = bits_.load(std::memory_order_relaxed);
while (true) {
if (ov & MASK) {
ov = bits_.load(std::memory_order_relaxed);
continue;
}
// compare_exchange_weak also updates ov if the exchange fails
if (bits_.compare_exchange_weak(ov, ov | MASK, std::memory_order_acq_rel)) {
break;
}
}
cppreference afirma questd::memory_order_relaxed
implica que "no hay restricciones en la reordenación de los accesos de memoria alrededor de la variable atómica", por lo que independientemente de lo que el hardware real hará o no hará, implica quebits_.load(std::memory_order_relaxed)
técnicamente podríaNunca leer un valor actualizado despuésbits_
¿Se actualiza en otro hilo en una implementación conforme?
EDITAR: Encontré esto en el estándar (29.4 p13):
Las implementaciones deben hacer que los almacenes atómicos sean visibles para las cargas atómicas dentro de un período de tiempo razonable.
Así que, aparentemente, esperar "infinitamente largo" por un valor actualizado está (¿en su mayoría?) Fuera de cuestión, pero no hay ninguna garantía sólida de ningún intervalo de tiempo específico de frescura que no sea "razonable"; aún así, la pregunta sobre el comportamiento real del hardware se mantiene.