2012-09-11 15 views
5

En Microsoft Visual C++ puedo llamar CreateThread() para crear un hilo iniciando una función con un parámetro void *. Paso un puntero a una estructura como ese parámetro, y veo que muchas otras personas también lo hacen.C++ - ¿Deberían los datos pasados ​​a un hilo ser volátiles?

Mi pregunta es si estoy pasando un puntero a mi estructura ¿cómo sé si los miembros de la estructura se han escrito realmente en la memoria antes de llamar a CreateThread()? ¿Hay alguna garantía de que no serán solo almacenados en caché? Por ejemplo:

struct bigapple { string color; int count; } apple; 
apple.count = 1; 
apple.color = "red"; 
hThread = CreateThread(NULL, 0, myfunction, &apple, 0, NULL); 

DWORD WINAPI myfunction(void *param) 
{ 
    struct bigapple *myapple = (struct bigapple *)param; 

    // how do I know that apple's struct was actually written to memory before CreateThread? 
    cout << "Apple count: " << myapple->count << endl; 
} 

Esta tarde mientras estaba leyendo vi una gran cantidad de código de Windows en este sitio web y otros que pasa en datos que no es volátil a un hilo, y no parece haber ninguna memoria barrera o cualquier otra cosa. Sé que C++ o al menos las revisiones más antiguas no son "conscientes de los hilos", así que me pregunto si tal vez haya alguna otra razón. Creo que el compilador ve que he pasado un puntero & apple en una llamada a CreateThread() para que sepa escribir miembros de apple antes de la llamada.

Gracias

+0

Inscripción en el hilo antes de imprimir ?! –

+1

Esto es C, no C++ ('struct bigapple *'?) –

Respuesta

5

n Las funciones de subproceso Win32 relevantes se ocupan de las barreras de memoria necesarias. Todas las escrituras anteriores a CreateThread son visibles para el nuevo subproceso. Obviamente, las lecturas en ese subproceso recién creado no pueden reordenarse antes de la llamada al CreateThread.

volatile no agregará ninguna restricción adicional útil en el compilador, y simplemente ralentizará el código. En la práctica, sin embargo, esto no sería notable en comparación con el costo de crear un nuevo hilo.

+0

1 Realmente no necesito esto mismo (usted y Raymond son más o menos las fuentes primarias para mí: P), pero creo el OP podría razonablemente pensar que [citación necesitada] ... – Mehrdad

+0

¿Qué pasa si en el hilo que iba a asignar apple-> count = 2. Cuando el hilo termina (a través de retorno normal de myfunction) se garantiza que 2 se escribió en la memoria ubicación en & apple-> count? Y además, ¿el código de llamada "se da cuenta" de eso? Por ejemplo asumir la CreateThread() llamada tiene éxito y hago la siguiente línea 'WaitForSingleObject (hThread, infinito);', y el siguiente 'cout << << apple-> recuento endl;', es que garantiza que el recuento será leído como 2 y no un caché como 1? Gracias. – loop

+0

@Mehrdad: ver p. http://msdn.microsoft.com/en-us/library/windows/desktop/ms686355(v=vs.85).aspx – MSalters

1

primer lugar, creo optimizador no puede cambiar el orden a costa de la corrección. CreateThread() es una función, el parámetro binidng para llamadas a funciones ocurre antes de se realiza la llamada.

En segundo lugar, volátil no es muy útil para el propósito que desea. Check out this article.

+3

La primera oración es claramente cierta. Un compilador absolutamente no puede cambiar el orden si eso rompe la corrección. Pero, ¿qué es correcto? Eso puede no ser siempre lo que esperas. Por ejemplo, la segunda oración muestra una suposición errónea. La escritura en 'apple.count' NO es parte del enlace de parámetros y puede reordenarse en relación con' & apple'. – MSalters

+0

@MSalters está bien, pero entonces, ¿cuál es la forma correcta de pasar un puntero a una estructura como parámetro de función y saber que el destinatario estará operando con datos válidos? Haciendo caso omiso de los hilos por un momento, si lo hago 'mystruct.myvar = myval; myfunc (& mystruct); 'Tenía la impresión de que myfunc() siempre leería myvar como myval. ¿Es eso correcto? – loop

+0

@test: Sin tener en cuenta los hilos, funciona porque el reordenamiento dentro de un hilo no romperá la semántica del hilo. La forma correcta de hacerlo en los hilos es una barrera de memoria. Estos están incluidos en las funciones de sincronización de hilos. P.ej. para Windows, proteger el acceso a 'apple' con una Sección Crítica también manejará las barreras de memoria necesarias. – MSalters

2

No, no debería ser volatile. Al mismo tiempo, señala el problema válido. El funcionamiento detallado de la memoria caché se describe en los documentos Intel/ARM/etc.

Sin embargo, puede asumir con seguridad que los datos SE ESCRITURA. De lo contrario, se romperán demasiadas cosas. Varias décadas de experiencia dicen que esto es así.

Si el programador de subprocesos iniciará el subproceso en el mismo núcleo, el estado de la memoria caché estará bien, de lo contrario, si no, el kernel vaciará la memoria caché. De lo contrario, nada funcionará.

Nunca utilice volatile para la interacción entre subprocesos. Es una instrucción sobre cómo manejar datos dentro del hilo solamente (use una copia de registro o siempre vuelva a leer, etc.).

+1

Totalmente de acuerdo con esto.Los riesgos de coherencia de los datos existen durante períodos cortos de tiempo, tal vez en los 10s de los ciclos del reloj central, y a menudo debido a los mecanismos de optimización de la CPU, como los búferes write-back y la captación previa de caché. Las arquitecturas SMP en las que las memorias caché no se comparten entre los núcleos tienen una gran cantidad de disposiciones de detección de caché en lugar de depender de la limpieza de caché en el cambio de contexto (que es muy caro). Es una suposición segura que cualquier sys-llamada es una barrera de memoria implícita (posiblemente incluso el cambio a los resultados de modo privilegiado en esto en muchas arquitecturas). – marko

+0

@Kirill ¿por qué dices nunca usar volátiles para la interacción entre hilos? – loop

+0

Porque 'volátil' no está destinado para esto. Su uso proporciona garantías solo para el mismo hilo. Si tiene 'volatile int a; b = a; b = a; ', el compilador no puede omitir la segunda asignación. La var 'a' debe ser roja dos veces. Pero cuántos hilos leerán/escribirán esta var al mismo tiempo, esto no está completamente especificado. No puedes hacer suposiciones como esa. –

0

Usted está luchando en un no-problema, y ​​está creando al menos otros dos ...

  1. No se preocupe por el parámetro dado a CreateThread: si es que existen en el momento es el hilo creados existen hasta que CreateThread regresa. Y dado que el hilo que los crea no los destruye, también están disponibles para el otro hilo.
  2. El problema se hace ahora quién y cuándo van a ser destruidos: Usted los crea con new por lo que existirán hasta que un delete se llama (o hasta que el proceso termina: buena pérdida de memoria)
  3. El proceso termina cuando su principal finalización de hilo (y todos los demás hilos también serán terminados por el SO!). Y no hay nada en tu main que lo haga esperar a que se complete el otro hilo.
  4. Tenga cuidado al usar API de bajo nivel como CreateThread los lenguajes de formulario que tienen su propia biblioteca también se interconectan con el hilo. El C-runtime tiene _beginthreadex.Llama al CreateThread y realiza también otra tarea de inicialización para la biblioteca C++ que de otra manera pasará por alto. Es posible que algunas funciones de bibliotecas C (y C++) no funcionen correctamente sin esas inicializaciones, que también son necesarias para liberar adecuadamente los recursos de tiempo de ejecución en la terminación. Unsing CreateThread es como usar malloc en un contexto donde delete se usa para limpiar.

El hilo principal bnehavior adecuada debe ser

// create the data 
// create the other thread 
// // perform othe task 
// wait for the oter thread to terminate 
// destroy the data 

Lo que la documentación de la API de Win32 no dicen claramente es que cada HANDLE es temporizador de espera, y se convierten en una señal cuando se libera el recurso asociado. que esperar a que la otra terminación de hilo, que el hilo principal, justo tendrá que llamar

WaitForSingleObject(hthread,INFINITE); 

De modo que el hilo principal será más adecuada:

{ 
    data* pdata = new data; 
    HANDLE hthread = (HANDLE)_beginthreadex(0,0,yourprocedure, pdata,0,0); 
    WaitForSingleObject(htread,INFINITE); 
    delete pdata; 
} 

o incluso

{ 
    data d; 
    HANDLE hthread = (HANDLE)_beginthreadex(0,0,yourprocedure, &d,0,0); 
    WaitForSingleObject(htread,INFINITE); 
} 
0

Creo que la pregunta es válida en otro contexto. Como otros han señalado usando una estructura y el contenido es seguro (aunque el acceso a los datos debe ser sincronizado).

Sin embargo, creo que la pregunta es válida si tiene una variable atómica (o un puntero a uno) que se puede cambiar fuera del hilo. Mi opinión en ese caso sería que se debe usar volátil en este caso.

Editar: creo que los ejemplos en la página wiki son una buena explicación http://en.wikipedia.org/wiki/Volatile_variable

Cuestiones relacionadas