2009-12-28 9 views
5

Una pregunta ayer sobre el bloqueo de doble verificación comenzó una cadena de pensamientos que me dejó inseguro sobre una situación simple. En el siguiente código, ¿es posible presionar printf de "Ya no está sincronizado"? En este ejemplo simple, los valores probablemente estarían en la misma línea de caché, por lo que creo que sería menos probable (suponiendo que la posibilidad sea> 0% para empezar).¿WaitForSingleObject funciona como una barrera de memoria?

Si la respuesta es "No, no es posible", entonces mi pregunta de seguimiento es, de manera bastante predecible: ¿por qué no? Hasta ayer, cuando enredé mis pensamientos y envolví el eje de múltiples hilos, asumí que el código estaría a salvo. Pero ahora me pregunto qué impide una lectura obsoleta del caché para una de las variables pa o pb. ¿Y importaría si pa, pb apuntara a variables simples globales de enteros en lugar de memoria malloc'd? ¿La llamada WaitForSingleObject proporciona una barrera de memoria? ¿O deberían los indicadores ser declarados volátiles? Tantas preguntas, tan pocas oraciones.

Actualización: Finalmente encontré información que dice específicamente que las funciones que señalan objetos de sincronización usan memory barriers. Debería haber sido obvio, pero estaba teniendo problemas para encontrar una respuesta definitiva. Así que una vez más puedo engañarme y hacerme creer que lo entiendo todo.

int i1 = 0; 
int i2 = 0; 
int reads = 0; 
int done = 0; 
int *pa = NULL; 
int *pb = NULL; 
HANDLE hSync = NULL; 

DWORD WriteThread(LPVOID pvParam) 
{ 
    while(!done) 
     { 
     WaitForSingleObject(hSync, INFINITE); 
     (*pa)++; 
     (*pb)++; 
     ReleaseSemaphore(hSync, 1, NULL); 
     } 
    return 0; 
} 

DWORD ReadThread(LPVOID pvParam) 
{ 
    while(!done) 
     { 
     WaitForSingleObject(hSync, INFINITE); 
     if (*pa != *pb) 
     { 
     printf("No longer in sync: %d, %d\n", *pa, *pb); 
     exit(1); 
     } 
     ReleaseSemaphore(hSync, 1, NULL); 
     reads++; 
     } 
    return 0; 
} 

int main(int argc, char* argv[]) 
{ 
    DWORD dwID; 

    // malloc'd memory 
    pa = (int*)malloc(sizeof(int)); 
    pb = (int*)malloc(sizeof(int)); 

    // Is a simple global variable different? 
    //pa = &i1; 
    //pb = &i2; 

    *pa = 0; 
    *pb = 0; 

    hSync = CreateSemaphore(NULL, 1, 1, NULL); 
    CreateThread(NULL, 0, WriteThread, NULL, 0, &dwID); 
    CreateThread(NULL, 0, ReadThread, NULL, 0, &dwID); 

    while (*pa < 1000000) 
     Sleep(1); 
    done = 1; 

    return 0; 
} 

Respuesta

4

No importa dónde reside la memoria, y si fuera todo acerca de la coherencia de caché, a continuación, declarando las variables volátiles no haría nada para solucionarlo. La semántica de Volatile no es ni necesaria ni suficiente para la seguridad de hilos; no lo uses!

En el nivel C/C++, pa y pb pueden almacenarse en caché en los registros, pero se considerarán obsoletos después de cualquier llamada de función. En el nivel de CPU, todas las funciones de espera usan barreras para asegurarse de que todo funcione como se espera.

+0

+1 exactamente lo que iba a decir. – tony

+0

Gracias por la información. ¿Conoce un enlace que discute las funciones de espera y las barreras de memoria? Eso es lo que estaba buscando y no lo vi. Es muy posible que solo quede ciego y me pierda algo obvio. –

+2

No eres ciego; es difícil encontrar información relevante en línea. MSDN ofrece una visión general razonablemente buena en http://msdn.microsoft.com/en-us/library/ms686355%28VS.85%29.aspx. –

Cuestiones relacionadas