En la programación informática , en particular en el C , C ++ , C # , y Java lenguajes de programación , la volátil palabra clave indica que un valor puede cambiar entre los diferentes accesos, aunque no parece que ser modificado. Esta palabra clave evita que un compilador optimizador optimice las lecturas o escrituras posteriores y, por lo tanto, reutilice incorrectamente un valor obsoleto u omita escrituras. Los valores volátiles surgen principalmente en el acceso al hardware ( E / S mapeada en memoria ), donde la lectura o escritura en la memoria se usa para comunicarse con dispositivos periféricos y en el subproceso, donde un subproceso diferente puede haber modificado un valor.
A pesar de ser una palabra clave común, el comportamiento de volatile
difiere significativamente entre los lenguajes de programación y se malinterpreta fácilmente. En C y C ++, es un calificador de tipo , como const
, y es una propiedad del tipo . Además, en C y C ++ no funciona en la mayoría de los escenarios de subprocesos, y se desaconseja su uso. En Java y C #, es una propiedad de una variable e indica que el objeto al que está vinculada la variable puede mutar y está específicamente diseñado para subprocesos. En el lenguaje de programación D , hay una palabra clave separada shared
para el uso de subprocesos, pero no volatile
existe ninguna palabra clave.
En C y C ++
En C y, en consecuencia, C ++, la volatile
palabra clave estaba destinada a [1]
- permitir el acceso a dispositivos de E / S asignados en memoria
- Permitir el uso de variables entre
setjmp
ylongjmp
- Permitir el uso de
sig_atomic_t
variables en manejadores de señales.
Las operaciones sobre las volatile
variables no son atómicas , ni establecen una relación adecuada de suceder antes del subproceso. Esto se especifica en los estándares relevantes (C, C ++, POSIX , WIN32), [1] y las variables volátiles no son seguras para subprocesos en la gran mayoría de las implementaciones actuales. Por lo tanto, volatile
muchos grupos de C / C ++ desaconsejan el uso de palabras clave como mecanismo de sincronización portátil. [2] [3] [4]
Ejemplo de E / S mapeadas en memoria en C
En este ejemplo, el código establece el valor almacenado en foo
a 0
. Luego comienza a sondear ese valor repetidamente hasta que cambia a 255
:
static int foo ; barra vacía ( vacío ) { foo = 0 ; mientras ( foo ! = 255 ) ; }
Un compilador optimizador notará que ningún otro código puede cambiar el valor almacenado foo
y asumirá que permanecerá igual 0
en todo momento. Por lo tanto, el compilador reemplazará el cuerpo de la función con un bucle infinito similar a este:
void bar_optimized ( vacío ) { foo = 0 ; mientras ( verdadero ) ; }
Sin embargo, foo
podría representar una ubicación que puede ser cambiada por otros elementos del sistema informático en cualquier momento, como un registro de hardware de un dispositivo conectado a la CPU . El código anterior nunca detectaría tal cambio; sin la volatile
palabra clave, el compilador asume que el programa actual es la única parte del sistema que podría cambiar el valor (que es, con mucho, la situación más común).
Para evitar que el compilador optimice el código como se indicó anteriormente, volatile
se utiliza la palabra clave:
estático volátil int foo ; barra vacía ( vacío ) { foo = 0 ; mientras ( foo ! = 255 ) ; }
Con esta modificación, la condición del bucle no se optimizará y el sistema detectará el cambio cuando ocurra.
Generalmente, hay operaciones de barrera de memoria disponibles en plataformas (que están expuestas en C ++ 11) que deberían ser preferidas en lugar de volátiles, ya que permiten que el compilador realice una mejor optimización y, lo que es más importante, garantizan un comportamiento correcto en escenarios de subprocesos múltiples; ni la especificación C (antes de C11) ni la especificación C ++ (antes de C ++ 11) especifican un modelo de memoria de subprocesos múltiples, por lo que es posible que los volátiles no se comporten de manera determinista en los sistemas operativos / compiladores / CPU). [5]
Comparación de optimización en C
Los siguientes programas en C y los ensamblados que los acompañan demuestran cómo la volatile
palabra clave afecta la salida del compilador. El compilador en este caso fue GCC .
Al observar el código ensamblador, se ve claramente que el código generado con volatile
objetos es más detallado, lo que lo hace más largo para volatile
que se pueda cumplir con la naturaleza de los objetos. La volatile
palabra clave evita que el compilador realice la optimización en el código que involucra objetos volátiles, asegurando así que cada asignación y lectura de variable volátil tenga un acceso a la memoria correspondiente. Sin la volatile
palabra clave, el compilador sabe que no es necesario volver a leer una variable de la memoria en cada uso, porque no debería haber ninguna escritura en su ubicación de memoria desde ningún otro hilo o proceso.
Comparación de ensamblajes | |
---|---|
Sin volatile palabra clave | Con volatile palabra clave |
# incluye | # incluye |
gcc -S -O3 -masm = intel noVolatileVar.c -o sin.s | gcc -S -O3 -masm = intel VolatileVar.c -o with.s |
.archivo "noVolatileVar.c" .intel_syntax noprefix .section .rodata.str1.1 , "aMS" , @progbits , 1 .LC0: .string "% d" .section .text.startup , "ax" , @progbits . p2align 4 ,, 15 .globl principal .type principal , función @ principal: .LFB11: .cfi_startproc sub RSP , 8 .cfi_def_cfa_offset 16 mov esi , 110 mov edi , OFFSET PLANA :. LC0 xor eax , eax llamar printf mov esi , 200 mov edi , OFFSET FLAT :. LC0 xor eax , eax llamar printf xor eax , eax agregar rsp , 8 .cfi_def_cfa_offset 8 ret .cfi_endproc .LFE11: .size main , .- Main .ident "GCC: (GNU) 4.8.2" .section .note.GNU- pila , "" , @progbits | .archivo "VolatileVar.c" .intel_syntax noprefix .section .rodata.str1.1 , "aMS" , @progbits , 1 .LC0: .string "% d" .section .text.startup , "ax" , @progbits . p2align 4 ,, 15 .globl principal .type principal , función @ principal: .LFB11: .cfi_startproc sub RSP , 24 .cfi_def_cfa_offset 32 mov edi , OFFSET PLANA :. LC0 mov DWORD PTR [ rsp ], 10 mov DWORD PTR [ rsp + 4 ], 100 mov DWORD PTR [ rsp + 8 ], 0 mov DWORD PTR [ rsp + 12 ], 0 mov esi , DWORD PTR [ rsp ] mov eax , DWORD PTR [ rsp + 4 ] agregar esi , eax xor eax , eax llamar printf mov eax , DWORD PTR [ rsp + 4 ] mov edi , OFFSET FLAT :. LC0 mov DWORD PTR [ rsp ], eax mov eax , DWORD PTR [ rsp + 4 ] mov DWORD PTR [ rsp + 8 ], eax mov eax , DWORD PTR [ rsp + 4 ] mov DWORD PTR [ rsp + 12 ], eax mov esi , DWORD PTR [ rsp + 8 ] mov eax , DWORD PTR [ rsp + 12 ] agregar esi , eax xor eax , eax llamar a printf xor eax , eax agregar rsp , 24 .cfi_def_cfa_offset 8 ret .cfi_endproc .LFE11: .size main , .-main .ident "GCC: (GNU) 4.8.2" .sección .note.GNU-stack , "" , @progbits |
C ++ 11
De acuerdo con el estándar ISO C ++ 11 , la palabra clave volátil solo está diseñada para su uso para el acceso al hardware; no lo utilice para la comunicación entre subprocesos. Para la comunicación entre subprocesos, la biblioteca estándar proporciona std::atomic
plantillas. [6]
En Java
El lenguaje de programación Java también tiene la volatile
palabra clave, pero se usa para un propósito algo diferente. Cuando se aplica a un campo, el calificador de Java volatile
proporciona las siguientes garantías:
- En todas las versiones de Java, hay un orden global en lecturas y escrituras de todas las variables volátiles (este orden global en volátiles es un orden parcial sobre el orden de sincronización más grande (que es un orden total sobre todas las acciones de sincronización )). Esto implica que cada hilo que acceda a un campo volátil leerá su valor actual antes de continuar, en lugar de (potencialmente) usar un valor almacenado en caché. (Sin embargo, no hay garantía sobre el orden relativo de lecturas y escrituras volátiles con lecturas y escrituras regulares, lo que significa que generalmente no es una construcción de subprocesos útil).
- En Java 5 o posterior, las lecturas y escrituras volátiles establecen una relación de suceder antes , muy similar a la adquisición y liberación de un mutex. [7] [8]
El uso volatile
puede ser más rápido que un bloqueo , pero no funcionará en algunas situaciones antes de Java 5. [9] El rango de situaciones en las que lo volátil es efectivo se expandió en Java 5; en particular, el bloqueo con doble verificación ahora funciona correctamente. [10]
C ª#
En C # , volatile
garantiza que el código que accede al campo no esté sujeto a algunas optimizaciones no seguras para subprocesos que pueden realizar el compilador, el CLR o el hardware. Cuando se marca un campo volatile
, se le indica al compilador que genere una "barrera de memoria" o "valla" a su alrededor, lo que evita el reordenamiento de instrucciones o el almacenamiento en caché vinculado al campo. Al leer un volatile
campo, el compilador genera un cercado de adquisición , que evita que otras lecturas y escrituras en el campo, incluidas las de otros subprocesos, se muevan antes del cercado. Al escribir en un volatile
campo, el compilador genera una barrera de liberación ; esta valla evita que otras lecturas y escrituras en el campo se muevan después de la valla. [11]
Sólo los siguientes tipos se pueden marcar volatile
: todos los tipos de referencia, Single
, Boolean
, Byte
, SByte
, Int16
, UInt16
, Int32
, UInt32
, Char
, y todos los tipos enumerados con un tipo de subyacente Byte
, SByte
, Int16
, UInt16
, Int32
, o UInt32
. [12] (este valor excluye estructuras , así como los tipos primitivos Double
, Int64
, UInt64
y Decimal
.)
El uso de la volatile
palabra clave no admite campos que se pasan por referencia o variables locales capturadas ; en estos casos, Thread.VolatileRead
y Thread.VolatileWrite
debe utilizarse en su lugar. [11]
En efecto, estos métodos deshabilitan algunas optimizaciones que normalmente realiza el compilador de C #, el compilador JIT o la propia CPU. Las garantías proporcionadas por Thread.VolatileRead
y Thread.VolatileWrite
son un superconjunto de las garantías proporcionadas por la volatile
palabra clave: en lugar de generar un "medio cercado" (es decir, un cercado de adquisición solo evita el reordenamiento de instrucciones y el almacenamiento en caché que viene antes), VolatileRead
y VolatileWrite
genera un "cercado completo" que evitar el reordenamiento de instrucciones y el almacenamiento en caché de ese campo en ambas direcciones. [11] Estos métodos funcionan de la siguiente manera: [13]
- El
Thread.VolatileWrite
método fuerza la escritura del valor en el campo en el punto de la llamada. Además, cualquier carga y almacenamiento de pedidos de programas anteriores debe ocurrir antes de la llamadaVolatileWrite
y cualquier carga y almacenamiento de pedidos de programas posteriores debe ocurrir después de la llamada. - El
Thread.VolatileRead
método fuerza la lectura del valor en el campo en el punto de la llamada. Además, cualquier carga y almacenamiento de pedidos de programas anteriores debe ocurrir antes de la llamadaVolatileRead
y cualquier carga y almacenamiento de pedidos de programas posteriores debe ocurrir después de la llamada.
Los métodos Thread.VolatileRead
y Thread.VolatileWrite
generan una cerca completa llamando al Thread.MemoryBarrier
método, que construye una barrera de memoria que funciona en ambas direcciones. Además de las motivaciones para usar una cerca completa dadas anteriormente, un problema potencial con la volatile
palabra clave que se resuelve usando una cerca completa generada por Thread.MemoryBarrier
es el siguiente: debido a la naturaleza asimétrica de las medias cercas, un volatile
campo con una instrucción de escritura seguida de una instrucción de lectura aún puede tener el orden de ejecución intercambiado por el compilador. Debido a que las vallas completas son simétricas, esto no es un problema al usarlas Thread.MemoryBarrier
. [11]
En Fortran
VOLATILE
es parte del estándar Fortran 2003 , [14] aunque la versión anterior lo admitía como una extensión. Hacer todas las variables volatile
en una función también es útil para encontrar errores relacionados con el alias .
entero , volátil :: i ! Cuando no se define como volátil, las siguientes dos líneas de código son idénticas, escriba ( * , * ) i ** 2 ! Carga la variable i una vez de la memoria y multiplica ese valor por sí mismo escribe ( * , * ) i * i ! Carga la variable i dos veces de la memoria y multiplica esos valores
Al "profundizar" siempre en la memoria de un VOLATILE, el compilador de Fortran no puede reordenar las lecturas o escrituras en volátiles. Esto hace visible a otros hilos las acciones realizadas en este hilo y viceversa. [15]
El uso de VOLATILE reduce e incluso puede evitar la optimización. [dieciséis]
Referencias
- ^ a b "Publicación sobre el comité de estándares de C ++" .
- ^ "Palabra clave volátil en Visual C ++" . Microsoft MSDN .
- ^ "Documentación del kernel de Linux: por qué no se debe utilizar la clase de tipo" volátil "" . kernel.org .
- ^ Scott Meyers; Andrei Alexandrescu (2004). "C ++ y los peligros del bloqueo comprobado" (PDF) . DDJ .
- ^ Jeremy Andrews (2007). "Linux: superstición volátil" . kerneltrap.org. Archivado desde el original el 20 de junio de 2010 . Consultado el 9 de enero de 2011 .
- ^ "volátil (C ++)" . Microsoft MSDN .
- ^ Sección 17.4.4: Orden de sincronización "La especificación del lenguaje Java®, Java SE 7 Edition" . Oracle Corporation . 2013 . Consultado el 12 de mayo de 2013 .
- ^ "Concurrencia de Java: comprensión de la palabra clave 'volátil'" . dzone.com. 2021-03-08. Archivado desde el original el 9 de mayo de 2021 . Consultado el 9 de mayo de 2021 .
- ^ Jeremy Manson; Brian Goetz (febrero de 2004). "Preguntas frecuentes sobre JSR 133 (modelo de memoria Java)" . Archivado desde el original el 9 de mayo de 2021 . Consultado el 5 de noviembre de 2019 .
- ^ Neil Coffey. "Bloqueo de doble verificación (DCL) y cómo solucionarlo" . Javamex . Consultado el 19 de septiembre de 2009 .
- ^ a b c d Albahari, Joseph. "Parte 4: Enhebrado avanzado" . Subprocesamiento en C # . O'Reilly Media. Archivado (PDF) desde el original el 27 de abril de 2011 . Consultado el 9 de diciembre de 2019 .
- ^ Richter, Jeffrey (11 de febrero de 2010). "Capítulo 7: Constantes y campos". CLR a través de C # . Microsoft Press. págs. 183 . ISBN 978-0-7356-2704-8.
- ^ Richter, Jeffrey (11 de febrero de 2010). "Capítulo 28: Construcciones de sincronización de subprocesos primitivos". CLR a través de C # . Microsoft Press. págs. 797 –803. ISBN 978-0-7356-2704-8.
- ^ "Declaración y atributo VOLATILE" . Cray.
- ^ "Matriz volátil y compartida en Fortran" . Intel.com .
- ^ "VOLÁTIL" . Oracle.com .
enlaces externos
- Manual de referencia de Ada C.6: Control de variable compartida
- Kernel de Linux: volátil-considerado-dañino