c++ multithreading mutex volatile memory-barriers

c++ - ¿Las funciones de bloqueo mutex son suficientes sin volátiles?



multithreading volatile (4)

Si el código anterior es correcto, ¿cómo es invulnerable a los problemas de almacenamiento en caché?

Hasta C ++ 0x, no lo es. Y no está especificado en C. Entonces, realmente depende del compilador. En general, si el compilador no garantiza que respetará las restricciones de ordenamiento en los accesos a la memoria para funciones u operaciones que involucren múltiples hilos, no podrá escribir código seguro multiproceso con ese compilador. Ver los hilos de Hans J Boehm no se puede implementar como una biblioteca .

En cuanto a las abstracciones que su compilador debería admitir para el código seguro de subprocesos, la entrada de wikipedia en Memory Barriers es un buen punto de partida.

(En cuanto a por qué la gente sugirió la volatile , algunos compiladores consideran volatile como una barrera de memoria para el compilador. Definitivamente no es estándar).

Un compañero de trabajo y yo escribimos software para una variedad de plataformas que se ejecutan en x86, x64, Itanium, PowerPC y otras CPU de servidores de 10 años.

Acabamos de tener una discusión sobre si las funciones mutex como pthread_mutex_lock () ... pthread_mutex_unlock () son suficientes por sí mismas, o si la variable protegida debe ser volátil.

int foo::bar() { //... //code which may or may not access _protected. pthread_mutex_lock(m); int ret = _protected; pthread_mutex_unlock(m); return ret; }

Mi preocupación es el almacenamiento en caché. ¿Podría el compilador colocar una copia de _protected en la pila o en un registro, y usar ese valor obsoleto en la tarea? Si no, ¿qué impide que eso suceda? ¿Son vulnerables las variaciones de este patrón?

Supongo que el compilador no comprende realmente que pthread_mutex_lock () es una función especial, entonces ¿estamos protegidos por los puntos de secuencia?

Muchas gracias

Actualización: Muy bien, puedo ver una tendencia con respuestas que explican por qué la volatilidad es mala. Respeto esas respuestas, pero los artículos sobre ese tema son fáciles de encontrar en línea. Lo que no puedo encontrar en línea, y el motivo por el que hago esta pregunta, es cómo estoy protegido sin volátil. Si el código anterior es correcto, ¿cómo es invulnerable a los problemas de almacenamiento en caché?


La palabra clave volátil es una sugerencia para el compilador de que la variable podría cambiar fuera de la lógica del programa, como un registro de hardware mapeado en memoria que podría cambiar como parte de una rutina de servicio de interrupción. Esto evita que el compilador suponga que un valor almacenado en caché es siempre correcto y normalmente obligaría a una lectura de memoria a recuperar el valor. Este uso es anterior al enhebrado en un par de décadas más o menos. Lo he visto usar con variables manipuladas por señales también, pero no estoy seguro de que el uso sea correcto.

Se garantiza que las variables protegidas por mutexes son correctas cuando se leen o escriben por hilos diferentes. La API de subprocesamiento es necesaria para garantizar que dichas vistas de variables sean consistentes. Este acceso es parte de la lógica de su programa y la palabra clave volátil es irrelevante aquí.


La respuesta más simple es volatile no es necesaria para multi-threading en absoluto.

La respuesta larga es que los puntos de secuencia, como las secciones críticas, dependen de la plataforma, como lo es la solución de subprocesamiento que está utilizando, por lo que la mayor parte de la seguridad de su subproceso también depende de la plataforma.

C ++ 0x tiene un concepto de subprocesos y seguridad de subprocesos pero el estándar actual no lo hace y por lo tanto volatile veces se identifica erróneamente como algo para evitar el reordenamiento de operaciones y el acceso a la memoria para la programación de subprocesamiento múltiple cuando nunca fue intencionado y no puede ser confiable usado de esa manera.

Lo único que debe usarse para volatile en C ++ es permitir el acceso a dispositivos mapeados en memoria, permitir el uso de variables entre setjmp y longjmp , y permitir el uso de variables sig_atomic_t en controladores de señales. La palabra clave en sí misma no hace que una variable sea atómica.

Buenas noticias en C ++ 0x tendremos la construcción std::atomic que se puede usar para garantizar operaciones atómicas y construir construcciones seguras para las variables. Hasta que el compilador de su elección lo soporte, es posible que tenga que recurrir a la biblioteca de impulso o extraer algún código ensamblador para crear sus propios objetos y proporcionar variables atómicas.

PD. Mucha de la confusión es causada por Java y .NET en realidad aplica la semántica de subprocesos múltiples con la palabra clave volatile C ++, sin embargo sigue el ejemplo con C donde este no es el caso.


Su biblioteca de subprocesos debe incluir las barreras adecuadas de CPU y compilador en el bloqueo y desbloqueo de mutex. Para GCC, un bloqueador de memory en una declaración de asm actúa como una barrera de compilación.

En realidad, hay dos cosas que protegen su código del almacenamiento en caché (compilador):

  • Está llamando a una función externa no pura ( pthread_mutex_*() ), lo que significa que el compilador no sabe que esa función no modifica sus variables globales, por lo que tiene que volver a cargarlas.
  • Como dije, pthread_mutex_*() incluye una barrera de compilación, por ejemplo: en glibc / x86 pthread_mutex_lock() termina llamando a la macro lll_lock() , que tiene un trozo de memory , obligando al compilador a recargar variables.