2009-05-13 7 views
9

Tengo 2 hilos y un float global compartido. Un hilo solo escribe en la variable mientras que el otro solo lo lee, ¿necesito bloquear el acceso a esta variable? En otras palabras:¿Necesito un bloqueo cuando solo un hilo escribe en una variable compartida?

volatile float x; 

void reader_thread() { 
    while (1) { 
     // Grab mutex here? 
     float local_x = x; 
     // Release mutex? 
     do_stuff_with_value(local_x); 
    } 
} 

void writer_thread() { 
    while (1) { 
     float local_x = get_new_value_from_somewhere(); 
     // Grab mutex here? 
     x = local_x; 
     // Release mutex? 
    } 
} 

Mi principal preocupación es que una carga o una tienda de float no ser atómica, de manera que local_x en reader_thread termina teniendo un valor falso, parcialmente actualizada.

  1. ¿Es esto una preocupación válida?
  2. ¿Hay alguna otra forma de garantizar la atomicidad sin un mutex?
  3. ¿Usaría sig_atomic_t como variable compartida, suponiendo que tiene suficientes bits para mis propósitos?

El lenguaje en cuestión es C usando pthreads.

+0

¿por qué quieres evitar el uso de un mutex? –

+0

El hilo del lector se ejecutará en cada cuadro de un juego como un complemento. Si bien es probable que sea una optimización prematura, quería evitar cualquier sobrecarga que pudiera. Dicho esto, estoy convencido por las respuestas de que debería usar un mutex, y dudo que esa cantidad de sobrecarga sea un problema. –

+0

Gracias por todas las respuestas. Parece que no debería contar con que la lectura/escritura sea atómica, especialmente para la portabilidad, por lo que el bloqueo parece ser el camino a seguir. –

Respuesta

12

Las diferentes arquitecturas tienen reglas diferentes, pero en general, las cargas de memoria y las tiendas de objetos alineados, int-size son atómicas. Más pequeño y más grande puede ser problemático. De modo que si sizeof(float) == sizeof(int) puede estar seguro, de todos modos no dependería de él en un programa portátil.

Además, el comportamiento de volatile no está particularmente bien definido ... La especificación lo usa como una forma de evitar la optimización de los accesos a la E/S del dispositivo mapeado en memoria, pero no dice nada sobre su comportamiento en ningún otro accesos de memoria.

En resumen, incluso si las cargas y las tiendas son atómica sobre float x, me gustaría utilizar barreras de memoria explícita (aunque cómo varía según la plataforma y el compilador) en lugar de depender de volatile. Sin la garantía de que las cargas y las tiendas sean atómicas, usted tiene para usar cerraduras, que sí implican barreras de memoria.

+0

+1 para barreras de memoria, comentario –

+0

"las cargas de memoria y las tiendas de objetos alineados de tamaño interno son atómicas" -> sí, pero la propogación de memoria no lo es. Por lo tanto, establecer x = 1 es indeterminadamente visible para otros núcleos. ** NO PUEDE ** utilizar este método con múltiples CPU.Incluso un bloqueo no lo ayuda aquí, a menos que contenga una barrera de memoria implícita, porque el bloqueo podría ir y venir mientras la escritura que usted quería * aún * no se ha propagado. –

+0

La propagación de la memoria entre CPU generalmente se realiza en caché, pero está claro que no se garantiza el orden. Sin embargo, todavía no he visto una biblioteca de bloqueo que no contenga barreras de memoria implícitas. – ephemient

0

Dado que es una sola palabra en la memoria que está cambiando, debería estar bien con solo la declaración volátil.

No creo que garantices que tendrás el último valor cuando lo leas, a menos que uses un candado.

+0

Explica el modo abajo, por favor. Estoy diciendo lo mismo que la respuesta aceptada, aunque más corta. – justinhj

+0

Voy a votar de nuevo para cancelar el -1 ya que no creo que tu respuesta merezca una respuesta negativa. Mi suposición es que quien votó negativamente lo hizo porque aunque puse los candados, eso aún no garantiza que tenga el último valor (el hilo del lector podría ejecutarse después de que el hilo del escritor reciba un nuevo valor, pero antes de que se lo asigne a la variable compartida). –

3

Lo bloquearía. No estoy seguro de cuán grande es float en su entorno, pero podría no leerse ni escribirse en una sola instrucción, por lo que su lector podría leer un valor medio escrito. Recuerde que volatile no dice nada acerca de la atomicidad de las operaciones, simplemente indica que la lectura vendrá de la memoria en lugar de estar en caché en un registro o algo así.

1

Creo que deberías usar un mutex. Algún día, su código podría ejecutarse en un sistema que no tiene hardware de punto flotante real y usa emulación en su lugar, lo que da como resultado variables de flotación no atómicas. Por ejemplo, mira -msoft-float here.

Mi respuesta no fue muy útil. gcc -msoft-float es posiblemente solo un caso específico donde las cargas y las tiendas de flotadores no son atómicas.

+0

No había pensado en esa posibilidad; ese es un buen punto. –

4

Según la sección 24.4.7.2 de la documentación de la biblioteca de C de GNU:

En la práctica, se puede asumir que no tenga más intint y otros tipos enteros son atómicos. También puede suponer que los tipos de puntero son atómicos; eso es muy conveniente Ambas suposiciones son ciertas en todas las máquinas compatibles con la biblioteca C de GNU y en todos los sistemas POSIX que conocemos.

float técnicamente no cuenta bajo estas reglas, aunque si un float es del mismo tamaño que un int en su arquitectura, lo que podría hacer es hacer que su variable global un int, y luego convertirlo a un flotador con un sindicato cada vez que lo lees o lo escribes.

El curso de acción más seguro es utilizar algún tipo de exclusión mutua para proteger los accesos a la variable compartida. Dado que las secciones críticas son extremadamente pequeñas (leer/escribir una única variable), es casi seguro que obtendrá un mejor rendimiento de un mutex liviano como un bloqueo de giro, a diferencia de un mutex pesado que hace que el sistema llama para hacer su trabajo.

+0

+1 Buen punto; Los bloqueos giratorios tienen un mejor rendimiento que los mecanismos de bloqueo más complejos cuando no hay contención. – ephemient

3

La asignación no es atómica, al menos para algunos compiladores, y en el sentido de que requiere una única instrucción. El siguiente código fue generado por Visual C++ 6.0 - f1 y f2 son de tipo float.

4:  f2 = f1; 
00401036 mov   eax,dword ptr [ebp-4] 
00401039 mov   dword ptr [ebp-8],eax 
+1

Sí, generalmente solo hay instrucciones de "mover la memoria para registrar" y "mover el registro a la memoria", no "mover la memoria a la memoria". Sin embargo, no creo que esto tenga una influencia especial en la pregunta de OP; si esto estuviera en reader_thread, "copy x to register" es atomic, y "copy register to local_x en my stack" es seguro; la situación es simétrica en writer_thread. reader_thread puede ver un valor ligeramente desactualizado, pero eso es mucho mejor que ver un valor inconsistente. – ephemient

0

Con toda probabilidad, no. Como no tiene posibilidad de colisión de escritura, la única preocupación es si puede leerlo mientras está medio escrito. Es muy poco probable que su código se ejecute en una plataforma donde escribir un flotante no ocurre en una sola operación si está escribiendo algo con subprocesos.

Sin embargo, es posible porque la definición de un flotador en C no obliga a que el hardware de almacenamiento subyacente se limitará a tamaño de palabra del procesador. Podría estar compilando el código máquina donde, por ejemplo, el signo y la mantisa están escritos en dos operaciones diferentes.

La verdadera pregunta, creo, es dos preguntas: "¿cuál es la desventaja de tener un mutex aquí?" y "¿Cuál es la repercusión si recibo una lectura de basura?"

Quizás en lugar de un mutex debe escribir una afirmación que determine si el tamaño de almacenamiento de un flotante es menor o igual al tamaño de palabra de la CPU subyacente.

Cuestiones relacionadas