2010-07-08 9 views
13

Considere lo siguiente C++ función miembro:Sincronización de acceso a un valor de retorno

size_t size() const 
{ 
    boost::lock_guard<boost::mutex> lock(m_mutex); 
    return m_size; 
} 

La intención aquí no es para sincronizar el acceso a la variable miembro privada m_size, pero sólo para asegurarse de que la persona que llama recibe un valor válido para m_size. El objetivo es evitar que la función regrese m_size al mismo tiempo que otro hilo está modificando m_size.

Pero, ¿hay alguna condición de carrera potencial para llamar a esta función? No estoy seguro de si el bloqueo de estilo RAII aquí es adecuado para proteger contra una condición de carrera. Supongamos que el destructor de la cerradura se llama antes de, ¿el valor de retorno de la función se inserta en la pila?

¿Tendría que hacer algo como lo siguiente para garantizar la seguridad del hilo?

size_t size() const 
{ 
    size_t ret; 

    { 
     boost::lock_guard<boost::mutex> lock(m_mutex); 
     ret = m_size; 
    } 

    return ret; 
} 
+0

No creo que tu cerradura realmente haga nada. Siempre que 'm_size' se pueda leer en su totalidad en una sola operación atómica, obtendrá un valor válido. –

+0

Esto no está garantizado en realidad. Sin embargo, el uso de 'std :: atomic' con C++ 0x garantizaría esto sin bloqueo. –

+0

@Mike: las cerraduras también son una barrera de memoria. Tal es necesario para sincronizar cachés de procesador, etc. – sbi

Respuesta

12

Ambos tu ejemplo construye hará lo que usted está buscando. La siguiente información de la norma es compatible con el comportamiento que está buscando (incluso en su primera ejemplo):

12,4/10 destructores:

Los destructores se invocan implícitamente ... para un objeto construido con cambio automático duración de almacenamiento (3.7.2) cuando sale el bloque en el que se crea el objeto.

Y, 6.6/2 declaraciones de salto (de los cuales return es uno):

En la salida de un ámbito de aplicación (sin embargo logrado), destructores (12.4) se llaman para todos los objetos construidos con almacenamiento automático duración (3.7.2) (objetos nombrados o temporales) que se declaran en ese ámbito, en el orden inverso al de su declaración. La transferencia de un bucle, de un bloque o de una variable inicializada con duración de almacenamiento automática implica la destrucción de variables con duración de almacenamiento automático que están en el alcance en el punto transferido desde, pero no en el punto transferido.

Así que en el momento de la return la variable lock es en su alcance y por lo tanto el dtor no ha sido llamado. Una vez que se ha ejecutado return, se llama al dtor para la variable lock (liberando así el bloqueo).

1

Su código inicial es correcto: se llamará al destructor después de que se haya almacenado el valor de retorno. ¡Este es el principio en el que opera RAII!

+3

¿Tiene alguna fuente para eso? –

3

Su primera variante es segura, sin embargo, no puede confiar en que este valor devuelto sea consistente durante un período de tiempo. Quiero decir, por ejemplo, no use ese valor devuelto en un ciclo for para iterar sobre cada elemento, porque el tamaño real podría cambiar inmediatamente después de la devolución.

Básicamente se puede pensar de esta manera: se necesita una copia del valor de retorno; de lo contrario, se llamaría al destructor y posiblemente se corrompería independientemente del valor devuelto antes de su devolución.

Se llama al destructor después de la declaración de devolución. Tome este ejemplo equivalente:

#include <assert.h> 

class A 
{ 
public: 
    ~A() 
    { 
     x = 10; 
    } 
    int x; 
}; 

int test() 
{ 
    A a; 
    a.x = 5; 
    return a.x; 
} 

int main(int argc, char* argv[]) 
{ 
    int x = test(); 
    assert(x == 5); 
    return 0; 
} 
+0

Después de pensarlo, veo que esta es la única forma en que el comportamiento podría funcionar. Cada 'return expr;' copia implícitamente el valor de 'expr' en un temporal, y cualquier objeto en el alcance puede referirse en' expr', por lo que debe estar activo en el momento en que se realiza la copia. (Las optimizaciones podrían eliminar la copia, por supuesto, pero el comportamiento debe ser "como si" estuviera allí). –

Cuestiones relacionadas