En programación informática , se dice que un valor es volátil si puede ser leído o modificado de forma asincrónica por algo distinto del hilo de ejecución actual . El valor de una volatile
variable puede cambiar espontáneamente por razones como: compartir valores con otros hilos; compartir valores con controladores de señales asincrónicos ; acceder a dispositivos de hardware a través de E/S mapeadas en memoria (donde puede enviar y recibir mensajes de dispositivos periféricos leyendo y escribiendo en la memoria). El soporte para estos casos de uso varía considerablemente entre los diversos lenguajes de programación que tienen la volatile
palabra clave. La volatilidad puede tener implicaciones con respecto a las convenciones de llamada de funciones y cómo se almacenan, acceden y almacenan en caché las variables.
En C y C++, volatile
es un calificador de tipo , como const
, y es parte de un tipo (por ejemplo, el tipo de una variable o campo).
El comportamiento de la volatile
palabra clave en C y C++ se da a veces en términos de suprimir optimizaciones de un compilador optimizador : 1- no eliminar volatile
lecturas y escrituras existentes, 2- no agregar nuevas volatile
lecturas y escrituras, y 3- no reordenar volatile
lecturas y escrituras. Sin embargo, esta definición es solo una aproximación para el beneficio de los nuevos estudiantes, y no se debe confiar en esta definición aproximada para escribir código de producción real.
En C, y en consecuencia en C++, la volatile
palabra clave tenía como objetivo: [1]
longjmp
.volatile
sig_atomic_t
objetos.Los estándares de C y C++ permiten escribir código portátil que comparte valores entre longjmp
objetos volatile
, y los estándares permiten escribir código portátil que comparte valores entre manejadores de señales y el resto del código en volatile
sig_atomic_t
objetos. Cualquier otro uso de volatile
la palabra clave en C y C++ es inherentemente no portátil o incorrecto. En particular, escribir código con la volatile
palabra clave para dispositivos de E/S mapeados en memoria es inherentemente no portátil y siempre requiere un profundo conocimiento de la implementación y la plataforma de C/C++ de destino específicas.
Es un error común pensar que la volatile
palabra clave es útil en código multihilo portable en C y C++. La volatile
palabra clave en C y C++ nunca ha funcionado como una herramienta útil y portable para ningún escenario multihilo. [2] [3] [4] [5] A diferencia de los lenguajes de programación Java y C# , las operaciones sobre volatile
variables en C y C++ no son atómicas , y las operaciones sobre volatile
variables no tienen suficientes garantías de ordenamiento de memoria (es decir, barreras de memoria ). La mayoría de los compiladores, enlazadores y tiempos de ejecución de C y C++ simplemente no proporcionan las garantías de ordenamiento de memoria necesarias para que la volatile
palabra clave sea útil para cualquier escenario multihilo. Antes de los estándares C11 y C++11, los programadores se veían obligados a confiar en las garantías de las implementaciones y plataformas individuales (por ejemplo, POSIX y WIN32) para escribir código multihilo . Con los estándares modernos C11 y C++11, los programadores pueden escribir código multihilo portable utilizando nuevas construcciones portables como las std::atomic<T>
plantillas. [6]
En este ejemplo, el código establece el valor almacenado en foo
. 0
Luego comienza a sondear ese valor repetidamente hasta que cambia a 255
:
int estático 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 en foo
y asumirá que permanecerá igual a 0
en todo momento. Por lo tanto, el compilador reemplazará el cuerpo de la función con un bucle infinito similar a este:
vacío bar_optimized ( vacío ) { foo = 0 ; mientras ( verdadero ) ; }
Sin embargo, el programador puede hacer foo
referencia a otro elemento del sistema informático, como un registro de hardware de un dispositivo conectado a la CPU , que puede cambiar el valor de foo
mientras se ejecuta este código. (Este ejemplo no incluye los detalles sobre cómo hacer foo
referencia a un registro de hardware de un dispositivo conectado a la CPU). Sin la volatile
palabra clave, un compilador optimizador probablemente convertirá el código de la primera muestra con la lectura en el bucle a la segunda muestra sin la lectura en el bucle como parte de la optimización de movimiento de código invariante de bucle común y, por lo tanto, es probable que el código nunca note el cambio que está esperando.
Para evitar que el compilador realice esta optimización, volatile
se puede utilizar la palabra clave:
estático volátil int foo ; barra vacía ( vacío ) { foo = 0 ; mientras ( foo != 255 ) ; }
La volatile
palabra clave evita que el compilador mueva la lectura fuera del bucle y, por lo tanto, el código notará el cambio esperado en la variable foo
.
Los siguientes programas en C y los extractos en lenguaje ensamblador 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 que volatile
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, lo que garantiza que cada asignación y lectura de variable volátil tenga un acceso de memoria correspondiente. Sin la volatile
palabra clave, el compilador sabe que no es necesario volver a leer una variable desde 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.
Si bien tanto C como C++ lo tienen previsto, el estándar C actual no expresa que la volatile
semántica se refiere al valor l, no al objeto referenciado. El informe de defectos correspondiente DR 476 (a C11) todavía está bajo revisión con C17 . [7]
A diferencia de otras características del lenguaje C y C++, la volatile
palabra clave no está bien soportada por la mayoría de las implementaciones de C/C++, incluso para usos portables de acuerdo con los estándares de C y C++. La mayoría de las implementaciones de C/C++ tienen errores en cuanto al comportamiento de la volatile
palabra clave. [8] [9] Los programadores deben tener mucho cuidado siempre que utilicen la volatile
palabra clave en C y C++.
En todas las versiones modernas del lenguaje de programación Java , la volatile
palabra clave ofrece las siguientes garantías:
volatile
Las lecturas y escrituras son atómicas . En particular, las lecturas y escrituras en long
los double
campos y no se romperán. (La garantía atómica se aplica únicamente al volatile
valor primitivo o al volatile
valor de referencia, y no a ningún valor de objeto).volatile
las lecturas y escrituras. En otras palabras, una volatile
lectura leerá el valor actual (y no un valor pasado o futuro), y todas volatile
las lecturas concordarán en un único orden global de volatile
escrituras.volatile
Las lecturas y escrituras tienen semánticas de barrera de memoria de "adquisición" y "liberación" (conocidas en el estándar Java como happen-before ). [10] [11] En otras palabras, volatile
proporciona garantías sobre el orden relativo de volatile
y no volatile
lecturas y escrituras. En otras palabras, volatile
proporciona básicamente las mismas garantías de visibilidad de memoria que un bloque sincronizado de Java (pero sin las garantías de exclusión mutua de un bloque sincronizado ).En conjunto, estas garantías forman volatile
una construcción multihilo útil en Java . En particular, el algoritmo de bloqueo de doble verificaciónvolatile
típico funciona correctamente en Java . [12]
Antes de la versión 5 de Java, el estándar Java no garantizaba el orden relativo de volatile
las operaciones de volatile
lectura y escritura. En otras palabras, volatile
no tenía semántica de barrera de memoria de "adquisición" y "liberación" . Esto limitaba en gran medida su uso como una construcción de subprocesos múltiples . En particular, el algoritmo de bloqueo de doble verificaciónvolatile
típico no funcionaba correctamente.
En C# , volatile
garantiza que el código que accede al campo no esté sujeto a algunas optimizaciones no seguras para subprocesos que pueden ser realizadas por el compilador, el CLR o por 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 la reordenación o el almacenamiento en caché de instrucciones vinculadas al campo. Al leer un volatile
campo, el compilador genera una acquire-fence , que evita que otras lecturas y escrituras en el campo se muevan antes de la valla. Al escribir en un volatile
campo, el compilador genera una release-fence ; esta valla evita que otras lecturas y escrituras en el campo se muevan después de la valla. [13]
Solo se pueden marcar los siguientes tipos volatile
: todos los tipos de referencia, Single
, Boolean
, Byte
, SByte
, Int16
, UInt16
, Int32
, UInt32
, Char
, y todos los tipos enumerados con un tipo subyacente de Byte
, SByte
, Int16
, UInt16
, Int32
o UInt32
. [14] ( Esto excluye las estructuras de valor , así como los tipos primitivos Double
, y ).Int64
UInt64
Decimal
El uso de la volatile
palabra clave no admite campos que se pasan por referencia o variables locales capturadas ; en estos casos, se debe utilizar Thread.VolatileRead
y en su lugar. [13]Thread.VolatileWrite
En efecto, estos métodos desactivan algunas optimizaciones que normalmente realizan 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 una "media valla" (es decir, una valla de adquisición solo impide la reordenación y el almacenamiento en caché de las instrucciones que vienen antes de ella), VolatileRead
y VolatileWrite
generan una "valla completa" que impide la reordenación y el almacenamiento en caché de las instrucciones de ese campo en ambas direcciones. [13] Estos métodos funcionan de la siguiente manera: [15]
Thread.VolatileWrite
método obliga a que el valor del campo se escriba en el momento de la llamada. Además, cualquier carga y almacenamiento de orden de programa anterior debe ocurrir antes de la llamada VolatileWrite
y cualquier carga y almacenamiento de orden de programa posterior debe ocurrir después de la llamada.Thread.VolatileRead
método obliga a que el valor del campo se lea en el punto de la llamada. Además, cualquier orden de programa anterior debe cargarse y almacenarse antes de la llamada VolatileRead
y cualquier orden de programa posterior debe cargarse y almacenarse después de la llamada.Los métodos Thread.VolatileRead
y Thread.VolatileWrite
generan una cerca completa al llamar 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 al usar 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 cercas completas son simétricas, esto no es un problema cuando se usa Thread.MemoryBarrier
. [13]
VOLATILE
es parte del estándar Fortran 2003 , [16] aunque la versión anterior lo admitía como extensión. Crear 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 dos líneas de código siguientes son idénticas. write ( * , * ) i ** 2 ! Carga la variable i una vez desde la memoria y multiplica ese valor por sí mismo. write ( * , * ) i * i ! Carga la variable i dos veces desde la memoria y multiplica esos valores.
Al "explorar" siempre la memoria de un VOLÁTIL, se evita que el compilador de Fortran reordene las lecturas o escrituras en los volátiles. Esto hace visibles para otros subprocesos las acciones realizadas en este subproceso, y viceversa. [17]
El uso de VOLATILE reduce e incluso puede impedir la optimización. [18]
{{cite web}}
: CS1 maint: bot: estado de URL original desconocido ( enlace )