2009-05-18 18 views
29

Si tengo un programa de subprocesos múltiples que lee una memoria del tipo de caché por referencia. ¿Puedo cambiar este puntero por el hilo principal sin poner en riesgo ninguno de los otros hilos que leen valores inesperados?¿Cambiar un puntero se considera una acción atómica en C?

Como lo veo, si el cambio es atómico, los otros hilos leerán el valor anterior o el más nuevo; nunca memoria aleatoria (o punteros nulos), ¿verdad?

Soy consciente de que probablemente debería utilizar métodos de sincronización de todos modos, pero todavía tengo curiosidad.

¿Los cambios de puntero son atómicos?

Actualización: Mi plataforma es de 64 bits de Linux (2.6.29), aunque me gustaría una respuesta multi-plataforma, así :)

Respuesta

23

Como han mencionado otros, no hay nada en el lenguaje C que lo garantice, y depende de su plataforma.

En la mayoría de las plataformas de escritorio contemporáneas, la ubicación de lectura/escritura en una ubicación alineada de tamaño de palabra será atómica. Pero eso realmente no resuelve su problema, debido a que el procesador y el compilador reordenan las lecturas y escrituras.

Por ejemplo, el siguiente código se rompe:

Tema A:

DoWork(); 
workDone = 1; 

Tema B:

while(workDone != 0); 

ReceiveResultsOfWork(); 

Aunque la escritura a workDone es atómica, en muchos sistemas no es el procesador no garantiza que la escritura en workDone será visible para otros procesadores antes de que las escrituras realizadas a través de DoWork() sean vi sible. El compilador también puede volver a ordenar la escritura en workDone antes de la llamada al DoWork(). En ambos casos, ReceiveResultsOfWork() podría comenzar a trabajar en datos incompletos.

Dependiendo de su plataforma, es posible que necesite insertar vallas de memoria y demás para garantizar un orden correcto. Esto puede ser muy complicado para hacerlo bien.

O simplemente use bloqueos. Mucho más simple, mucho más fácil de verificar como correcto, y en la mayoría de los casos más que suficiente.

+0

En realidad, no creo que un compilador C (o C++) pueda volver a ordenar workDone = 1 ANTES de una llamada de función que lo precede, a menos que sepa algo más sobre workDone (como la palabra clave "noalias" nunca aprobada). Por otro lado, si tuviera foo = bar; qwerty = uiop; workDone = 1, luego workDone = 1 podría moverse (o incluso temporalmente ser un valor de solo registro, si workDone no es volátil). Si ReceiveResultsOfWork mira qwerty o foo, es posible que no obtenga uiop o bar, aunque workDone sea 1. Tendría que verificar qué sucede si DoWork() termina en línea. – jesup

+3

El compilador puede hacer ese reordenamiento si puede demostrar que DoWork no tiene acceso a workDone de ninguna manera definida. Esto realmente puede suceder si DoWork es lo suficientemente pequeño y está en la misma unidad de traducción y el compilador decide alinearlo. – derobert

+0

Lo importante es que el compilador no está obligado por el estándar para asegurarse de que se escriba workDone después de la llamada a DoWork(). En la práctica, definitivamente es correcto: si DoWork() es una llamada de función real, el compilador probablemente no reordene. Si DoWork está incluido, workDone podría reordenarse fácilmente antes de DoWork() o durante él. – Michael

12

lenguaje C El dice nada acerca de si las operaciones son atómicos. He trabajado en microcontroladores con buses de 8 bits y punteros de 16 bits; cualquier operación de puntero en estos sistemas sería potencialmente no atómica. Creo que recuerdo que los Intel 386 (algunos de los cuales tenían buses de 16 bits) planteaban preocupaciones similares. Del mismo modo, puedo imaginar sistemas que tienen CPU de 64 bits, pero buses de datos de 32 bits, lo que podría implicar preocupaciones similares sobre las operaciones de puntero no atómicas. (No he comprobado si existen tales sistemas.)

EDIT: Michael's answer merece la pena leerlo. El tamaño del bus frente al tamaño del puntero no es la única consideración con respecto a la atomicidad; fue simplemente el primer contraejemplo que me vino a la mente.

+1

por lo que el puntero podría estar medio cargado en un cambio de contexto? – ojblass

+1

@ojblass: Tales cosas han sucedido en arquitecturas antiguas; pueden suceder incluso con los más nuevos. (Vea mi comentario actualizado.) –

+1

Aprende algo todos los días ... gracias ... – ojblass

5

No mencionó una plataforma. Así que creo que una pregunta un poco más precisa sería

¿Se garantiza que los cambios del puntero son atómicos?

La distinción es necesaria porque las diferentes implementaciones de C/C++ pueden variar en este comportamiento. Es posible que una plataforma en particular garantice asignaciones atómicas y aún esté dentro del estándar.

En cuanto a si esto está garantizado en general en C/C++, la respuesta es No. El estándar C no ofrece tales garantías. La única forma de garantizar una asignación de puntero es atómica, es usar un mecanismo específico de plataforma para garantizar la atomicidad de la asignación. Por ejemplo, los métodos de Enclavamiento en Win32 proporcionarán esta garantía.

¿En qué plataforma estás trabajando?

4

La respuesta de desactivación es que la especificación C no requiere una asignación de puntero para ser atómica, por lo que no puede contar con que sea atómica.

La respuesta real sería que probablemente dependa de su plataforma, compilador y posiblemente la alineación de las estrellas el día que escribió el programa.

+2

Eso no es una salida de emergencia; es la respuesta correcta. Si está utilizando concurrencia, * necesita * tenerlo correcto; de lo contrario, perderás aún más tiempo tratando de replicar un Heisenbug de uno en uno. –

1

Lo único garantizado por el estándar es el tipo sig_atomic_t.

Como ha visto por las otras respuestas, es probable que esté bien cuando se apunta a la arquitectura genérica x86, pero es muy arriesgado con más hardware "especializado".

Si realmente está desesperado por saber, puede comparar sizeof (sig_atomic_t) a sizeof (int *) y ver cuál es su sistema de destino.

1

Resulta ser una pregunta bastante compleja. Le pregunté a similar question y leí todo lo que me señalaron. Aprendí mucho sobre cómo funciona el almacenamiento en caché en las arquitecturas modernas, y no encontré nada que fuera definitivo. Como han dicho otros, si el ancho del bus es menor que el ancho del bit del puntero, es posible que tenga problemas. Específicamente si los datos se encuentran en un límite de la línea de caché.

Una arquitectura prudente utilizará un bloqueo.

3

modificación del puntero 'normal' no se garantiza que sea atómica.

marque 'Compare and Swap' (CAS) y otras operaciones atómicas, no un estándar C, pero la mayoría de los compiladores tienen acceso a las primitivas del procesador. en el caso GNU gcc, hay varios built-in functions

Cuestiones relacionadas