2012-03-21 17 views
11

Supongamos que tengo un programa que inicializa una variable global para el uso de hilos, como los siguientes:pasar de forma segura los datos de sólo lectura a un nuevo hilo

int ThreadParameter; 

// this function runs from the main thread 
void SomeFunction() { 
    ThreadParameter = 5; 

    StartThread(); // some function to start a thread 
    // at this point, ThreadParameter is NEVER modified. 
} 

// this function is run in a background worker thread created by StartThread(); 
void WorkerThread() { 
    PrintValue(ThreadParameter); // we expect this to print "5" 
} 

Estas preguntas deben aplicarse para cualquier arquitectura de procesador genérico que uno podría encontrarse. Quiero que la solución sea portátil, no específica para una arquitectura con mayores garantías de memoria, como x86.

  1. Pregunta general: a pesar de ser muy común, ¿es esto realmente seguro en todas las arquitecturas de procesador? Cómo hacerlo seguro, si no?
  2. La variable global no es volatile; ¿Es posible que se reordene después de la llamada StartThread() y me deje manguera? ¿Cómo arreglar este problema?
  3. Supongamos que la computadora tiene dos procesadores que tienen sus propios cachés. El hilo principal se ejecuta en el primer procesador y el hilo de trabajo se ejecuta en el segundo procesador. Supongamos que el bloque de memoria que contiene ThreadParameter ha sido paginado en la memoria caché de cada procesador antes de que el programa comience a ejecutarse SomeFunction(). SomeFunction() escribe 5 en ThreadParameter, que se almacena en la caché del primer procesador, y luego inicia la cadena de trabajo, que se ejecuta en el segundo procesador. ¿No verá WorkerThread() en el segundo procesador datos no inicializados para ThreadParameter en lugar del valor esperado de 5, ya que la página de memoria en el segundo procesador aún no ha visto la actualización desde el primer procesador?
  4. Si se requiere algo diferente, ¿cuál es la mejor manera de manejar esto, dado que en lugar de un simple int, podría estar trabajando con un puntero a tipos de datos mucho más complejos que no necesariamente se usan en un entorno multiproceso?

Si mis preocupaciones son infundadas, ¿cuáles son las razones específicas por las que no tengo que preocuparme?

+0

SomeFunction va a llamar a varios WorkerThreads, supongo. Creo que su pregunta es más como: "¿Las lecturas simultáneas me causarán un problema?" ....? – Alex

+0

Bueno, cuando se utilizan los hilos POSIX se trabaja alrededor del enigma de la inicialización de variable global usando ['pthread_once()'] (http://linux.die.net/man/3/pthread_once) - No estoy seguro de cómo se traduce esto a su definición de "portátil". –

+0

Imagino que la mejor solución sería 'boost: shared_mutex' y copiando el read-only en cada thread. – Pubby

Respuesta

2

De ti descripción, parece que estás escribiendo a ThreadParameter (o alguna otra estructura de datos) ANTES de iniciar cualquier subproceso secundario, y nunca volverá a escribir en ThreadParameter ... existe para que se lea según sea necesario, pero nunca se vuelva a cambiar después de su inicialización; ¿Es eso correcto? Si es así, no hay necesidad de emplear ninguna llamada de sistema de sincronización de subprocesos (o primitivas de procesador/compilador) cada vez que un subproceso secundario desea leer los datos, o incluso la primera vez para ese asunto.

El tratamiento de volátiles es un tanto específico del compilador; Sé que al menos con Diab para PowerPC, hay una opción del compilador con respecto al tratamiento de la volatilidad: utilice la instrucción PowerPC EIEIO (o MBAR) después de cada lectura/escritura en una variable, o no lo use ... esto además de prohibir las optimizaciones del compilador asociadas con la variable. (EIEIO/MBAR es la instrucción de PowerPC para prohibir el reordenamiento de E/S por parte del procesador, es decir, todas las E/S anteriores a la instrucción deben completarse antes de cualquier E/S después de la instrucción).

Desde el punto de vista de la corrección/seguridad, no está de más declararlo como volátil. Pero desde un punto de vista pragmático, si inicias ThreadParameter lo suficientemente lejos antes de StartThread(), declararlo volátil realmente no debería ser necesario (y no hacerlo aceleraría todos los accesos posteriores). Casi cualquier llamada de función sustancial (digamos, tal vez a printf() o cout, o cualquier llamada al sistema, etc.) emitiría órdenes de magnitud más instrucciones de las necesarias para garantizar que no haya forma de que el procesador no haya manejado la escritura hace mucho tiempo. ThreadParameter antes de su llamada a StartThread(). Siendo realistas, StartThread() con seguridad ejecutará instrucciones suficientes antes de que el hilo en cuestión realmente comience. Así que estoy sugiriendo que realmente no necesita declararlo volátil, probablemente ni siquiera si lo inicializa inmediatamente antes de llamar a StartThread().

Ahora en cuanto a su pregunta sobre qué pasaría si la página que contiene esa variable ya estuviera cargada en la caché de ambos procesadores antes de que el procesador ejecute la inicialización inicial: Si está utilizando una plataforma de propósito general comúnmente disponible con CPU de tipo similar, el hardware ya debería estar en su lugar para manejar la coherencia de caché para usted. El lugar en el que tiene problemas con la coherencia del caché en plataformas de uso general, ya sea multiprocesador o no, es cuando el procesador tiene cachés de datos de instrucciones separadas & y escribe código de auto modificación: las instrucciones escritas en la memoria no se pueden distinguir de los datos por lo que la CPU no invalida esas ubicaciones en la caché de instrucciones, por lo que puede haber instrucciones obsoletas en la caché de instrucciones a menos que posteriormente invalide esas ubicaciones en la caché de instrucciones (ya sea emitiendo sus propias instrucciones de ensamblaje específicas del procesador, que puede que no sea permitido hacer dependiendo de su sistema operativo y el nivel de privilegios de su subproceso, o bien emitir la llamada del sistema invalidación de caché correspondiente para su sistema operativo). Pero lo que estás describiendo no es un código de modificación automática, por lo que deberías estar a salvo en ese sentido.

Su pregunta 1 pregunta cómo hacer esto seguro en TODAS las arquitecturas de procesador. Bueno, como mencioné anteriormente, deberías estar seguro si estás usando procesadores de tipo similar cuyos buses de datos están puenteados adecuadamente. Los procesadores de propósito general diseñados para la interconexión de multiprocesadores tienen protocolos de bus snoop para detectar escrituras en la memoria compartida ... siempre y cuando la biblioteca de threading configure correctamente la región de memoria compartida. Si está trabajando en un sistema integrado, puede que tenga que configurarlo usted mismo en su BSP ... para PowerPC, necesita mirar los bits de WIMG en su configuración de MMU/BAT; No estoy familiarizado con otras arquitecturas para darte consejos sobre eso. PERO .... Si su sistema es casero o si sus procesadores no son del mismo tipo, es posible que no pueda contar con que los dos procesadores puedan husmear las escrituras de los demás; Consulte con su equipo de hardware para obtener consejos.

+0

Si solo está escribiendo ThreadParameter _once_ y, suponiendo que esté utilizando un lenguaje y compilador de buen comportamiento (C++), cree la variable lo más tarde posible, preferiblemente al inicializarla, y haga que ** const **. Si va a modificar ThreadParameter en el hilo principal y espera que los hilos hijo respeten ese cambio, intente poner una condición de espera en la variable. Esperarás a que el hilo principal escriba ThreadParameter antes de usarlo y podrás comprobar si hay nuevas actualizaciones pendientes mientras se ejecuta el subproceso secundario. Un procedimiento bien escrito puede retroceder y volver a ejecutar con el valor actualizado. –

3

Cuando crea un nuevo hilo, la construcción del hilo sincroniza con el inicio de la función de hilo. Eso significa que está bien: escribe ThreadParameter antes de crear el hilo, y los hilos lo acceden después de que se inician, por lo que puede estar seguro de que se escribe antes de leer, por lo que se garantiza que los hilos verán el valor correcto

(se requiere que el compilador para asegurar que todas las escrituras hechas antes de que el hilo se inicia son visibles en el nuevo hilo.)

1
  1. Sí, es seguro.
  2. No lo sé. Quizás: if(ThreadParameter = 5) StartThread();. Sin embargo, en general, trate de no adivinar el compilador.
  3. Probablemente no. Si tenía que preocuparse por los detalles de niveles tan bajos al escribir el código, entonces la lógica que controla cómo se ejecuta un programa en una máquina de varios núcleos probablemente no está haciendo su trabajo muy bien.
  4. Boost es su amigo para trabajar con tipos complejos en un entorno de subprocesos múltiples.
Cuestiones relacionadas