2010-10-25 14 views
9

Estoy experimentando con el soporte de C++ 0x y hay un problema, que supongo que no debería estar allí. O no entiendo el tema o gcc tiene un error.Problemas de ordenación de memoria

Tengo el siguiente código, inicialmente x y y son iguales. El hilo 1 siempre incrementa x primero y luego incrementa y. Ambos son valores enteros atómicos, por lo que no hay ningún problema con el incremento en absoluto. El hilo 2 está comprobando si el x es menor que y y muestra un mensaje de error si es así.

Este código falla a veces, pero ¿por qué? El problema aquí es probablemente el reordenamiento de la memoria, pero todas las operaciones atómicas son consecutivamente consistentes por defecto y no me relajé explícitamente de esas operaciones. Estoy compilando este código en x86, que, por lo que sé, no debería tener problemas con el pedido. ¿Puedes explicar cuál es el problema?

#include <iostream> 
#include <atomic> 
#include <thread> 

std::atomic_int x; 
std::atomic_int y; 

void f1() 
{ 
    while (true) 
    { 
     ++x; 
     ++y; 
    } 
} 

void f2() 
{ 
    while (true) 
    { 
     if (x < y) 
     { 
      std::cout << "error" << std::endl; 
     } 
    } 
} 

int main() 
{ 
    x = 0; 
    y = 0; 

    std::thread t1(f1); 
    std::thread t2(f2); 

    t1.join(); 
    t2.join(); 
} 

El resultado se puede ver here.

+0

Actualmente esta es una implementación experimental de C++ 0x, por lo que la segunda es posible, pero creo que la primera es más probable: P – confucius

+3

El código publicado arriba siempre producirá '" error "', ('x' siempre será mayor o igual a 'y') ¿es esto lo que querías? – Paul

+0

¿Por qué si (x confucius

Respuesta

11

El problema podría estar en su prueba:

if (x < y) 

el hilo podría evaluar x y no moverse a evaluar y hasta mucho más tarde.

+0

Supongo que ese es el problema, la pregunta es ¿por qué? En realidad, las operaciones atómicas secuencialmente consistentes debe evitar esto, ¿no? – confucius

+0

¡Muchas gracias! roblem es una secuencia de evaluación de expresiones no especificada, tan simple :) – confucius

+4

@confucius: mientras que su escenario puede tener una dependencia en el orden en que las variables pueden leerse, el problema más general es que la lectura de 2 instancias atómicas diferentes no es atómica. –

12

Hay un problema con la comparación:

x < y 

El orden de evaluación de subexpresiones (en este caso, de x y y) no se especifica, por lo y pueden ser evaluados antes de x o x se puede evaluar antes de y.

Si x se lee en primer lugar, tiene un problema:

x = 0; y = 0; 
t2 reads x (value = 0); 
t1 increments x; x = 1; 
t1 increments y; y = 1; 
t2 reads y (value = 1); 
t2 compares x < y as 0 < 1; test succeeds! 

Si se asegura explícitamente que y se lee en primer lugar, se puede evitar el problema:

int yval = y; 
int xval = x; 
if (xval < yval) { /* ... */ } 
+0

¡Gracias! Eso es. Lo siento muchachos, no puedo más, https está bloqueado en la oficina y no puedo iniciar sesión :( – confucius

-3

En primer lugar, estoy de acuerdo con "Michael Burr" y "James McNellis". Su prueba no es justa, y existe una posibilidad legítima de fallar. Sin embargo, incluso si reescribe la prueba de la manera en que "James McNellis" sugiere que la prueba puede fallar.

La primera razón para esto es que no usa semántica volatile, por lo tanto, el compilador puede hacer optimizaciones para su código (que se supone que está bien en un caso de subproceso único).

Pero incluso con volatile no se garantiza que su código funcione.

Creo que no entiende completamente el concepto de reordenación de memoria. En realidad, el reorden de lectura/escritura de memoria puede ocurrir en dos niveles:

  1. El compilador puede cambiar el orden de las instrucciones de lectura/escritura generadas.
  2. La CPU puede ejecutar instrucciones de lectura/escritura de memoria en orden arbitrario.

El uso de volatile impide que (1). Sin embargo, no ha hecho nada para evitar (2) la reordenación del acceso a la memoria mediante el hardware .

para evitar que esto se debería poner especial valla memoria instrucciones en el código (que se designan para la CPU, a diferencia de volatile que es para compilador sólo).

En x86/x64 hay muchas instrucciones diferentes para la valla de memoria. Además, cada instrucción con semántica lock de forma predeterminada emite una valla de memoria completa.

Más información aquí:

http://en.wikipedia.org/wiki/Memory_barrier

+2

Los atómicos C++ 0x garantizan la correcta ordenación de la memoria –

+1

valdo - no es necesario usar volátil aquí, porque las barreras de memoria por defecto generan C++ 0x las operaciones atómicas previenen (1) y (2). – confucius

4

De vez en cuando, x envolverá a 0 justo antes y se envuelve alrededor de cero. En este punto, y será legítimamente mayor que x.

+2

.También se observó que el desbordamiento firmado genera un comportamiento indefinido, aunque el desbordamiento no firmado se ajusta como se esperaba. – GManNickG

+0

De acuerdo. se supone que suceda, fue hecho solo para prueba, pero esto también podría ser un problema. Gracias. – confucius

Cuestiones relacionadas