En programación , se dice que una variable es volátil si su valor puede leerse o modificarse de forma asíncrona por un proceso distinto al hilo de ejecución actual . El valor de una volatilevariable puede cambiar espontáneamente por motivos como: compartir valores con otros hilos; compartir valores con manejadores de señales asíncronos ; acceder a dispositivos de hardware mediante E/S mapeada en memoria (donde los mensajes de los dispositivos periféricos se pueden recibir y enviar leyendo y escribiendo en la memoria). La compatibilidad con estos casos de uso varía considerablemente entre los lenguajes de programación que incluyen la volatilepalabra clave `volatile`. La volatilidad puede tener implicaciones en las convenciones de llamada a funciones y en cómo se almacenan, acceden y almacenan en caché las variables.
En C y C++
En C y C++, volatilees un calificador de tipo , como const, y es parte de un tipo (por ejemplo, el tipo de una variable o campo).
En C y C++, el comportamiento de la volatilepalabra clave `require` a veces se describe en términos de suprimir las optimizaciones de un compilador optimizador : 1) no eliminar volatilelas lecturas y escrituras existentes, 2) no agregar nuevas volatilelecturas y escrituras, y 3) no reordenar volatilelas lecturas y escrituras. Sin embargo, esta definición es solo una aproximación para quienes se inician en el lenguaje, y no se debe confiar en ella para escribir código de producción real.
En C, y por consiguiente en C++, la volatilepalabra clave tenía como objetivo: [ 1 ]
- Permitir el acceso a los dispositivos de E/S mapeados en memoria .
- Permitir la conservación de valores a través de un
longjmp. - Permitir compartir valores entre los manejadores de señales y el resto del programa en
volatilesig_atomic_tobjetos.
Los estándares de C y C++ permiten escribir código portable que comparte valores entre longjmpobjetos volatile, y también permiten escribir código portable que comparte valores entre manejadores de señales y el resto del código dentro de volatilesig_atomic_tlos objetos. Cualquier otro uso de volatilela palabra clave en C y C++ es inherentemente no portable o incorrecto. En particular, escribir código con la volatilepalabra clave para dispositivos de E/S mapeados en memoria es inherentemente no portable y siempre requiere un conocimiento profundo de la implementación y plataforma específicas de C/C++ de destino.
Multihilo
Es un error común pensar que la volatilepalabra clave es útil en código multihilovolatile portable en C y C++. La palabra clave en C y C++ nunca ha funcionado como una herramienta portable útil para ningún escenario multihilo. [ 2 ] [ 3 ] [ 4 ] [ 5 ] A diferencia de los lenguajes de programación Java y C# , las operaciones con volatilevariables en C y C++ no son atómicas , y las operaciones con volatilevariables no tienen suficientes garantías de orden de memoria (es decir, barreras de memoria ). La mayoría de los compiladores, enlazadores y entornos de ejecución de C y C++ simplemente no proporcionan las garantías de orden de memoria necesarias para que la volatilepalabra clave sea útil para cualquier escenario multihilo. Antes de los estándares C11 y C++11 , los programadores se veían obligados a depender de las garantías de las implementaciones y plataformas individuales (por ejemplo, POSIX y WIN32) para escribir código multihilo . Con los modernos estándares C11 y C++11, los programadores pueden escribir código multihilo portable utilizando nuevas construcciones portables como las std::atomic<T>plantillas. [ 6 ]
Ejemplo de E/S mapeada en memoria en C
En este ejemplo, el código establece el valor almacenado en foo. 0Luego comienza a consultar ese valor repetidamente hasta que cambie a 255:
entero estático foo ;void bar ( void ) { 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 en 0todo momento. Por lo tanto, el compilador reemplazará el cuerpo de la función con un bucle infinito similar a este:
void bar_optimized ( void ) { foo = 0 ;mientras ( verdadero ) {} }Sin embargo, el programador puede hacer fooreferencia a otro elemento del sistema informático, como un registro de hardware de un dispositivo conectado a la CPU , que puede cambiar el valor foomientras se ejecuta este código. (Este ejemplo no incluye detalles sobre cómo hacer fooreferencia a un registro de hardware de un dispositivo conectado a la CPU). Sin la volatilepalabra 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 común de movimiento de código invariante de bucle , 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, volatilese puede utilizar la palabra clave:
entero estático volátil foo ;void bar ( void ) { foo = 0 ;mientras ( foo != 255 ) {} }La volatilepalabra clave impide que el compilador mueva la lectura fuera del bucle y, por lo tanto, el código detectará el cambio esperado en la variable foo.
Comparación de optimización en C
Los siguientes programas en C, junto con los fragmentos de código ensamblador que los acompañan, demuestran cómo la volatilepalabra clave afecta la salida del compilador. En este caso, el compilador utilizado fue GCC .
Al observar el código ensamblador, se aprecia claramente que el código generado con volatileobjetos es más extenso, lo que volatilepermite cumplir con la naturaleza de los objetos. La volatilepalabra clave `volatile` impide que el compilador optimice el código que involucra objetos volátiles, asegurando así que cada asignación y lectura de una variable volátil tenga su correspondiente acceso a memoria. Sin esta volatilepalabra clave, el compilador sabe que una variable no necesita releerse de la memoria en cada uso, ya que no debería haber escrituras en su ubicación de memoria desde ningún otro hilo o proceso.
Defectos del compilador
A diferencia de otras características del lenguaje C y C++, la volatilepalabra clave `new` no cuenta con un buen soporte en la mayoría de las implementaciones de C/C++, incluso para usos portables según los estándares de C y C++. La mayoría de las implementaciones de C/C++ presentan errores en el comportamiento de la volatilepalabra clave `new`. [ 7 ] [ 8 ] Los programadores deben tener mucho cuidado al usar la volatilepalabra clave `new` en C y C++.
En Java
En todas las versiones modernas del lenguaje de programación Java , la volatilepalabra clave ofrece las siguientes garantías:
volatileLas lecturas y escrituras son atómicas . En particular, las lecturas y escrituras enlongcamposdoubleno se interrumpirán. (La garantía atómica se aplica solo alvolatilevalor primitivo o alvolatilevalor de referencia, y no a ningún valor de objeto).- Existe un único orden global para todas
volatilelas lecturas y escrituras. En otras palabras, unavolatilelectura leerá el valor actual (y no un valor pasado o futuro), y todasvolatilelas lecturas coincidirán en un único orden global devolatileescrituras. volatileLas lecturas y escrituras tienen semántica de barrera de memoria de "adquisición" y "liberación" (conocida en el estándar Java como happen-before ). [ 9 ] [ 10 ] En otras palabras,volatileproporciona garantías sobre el orden relativo de las lecturas y escrituras yvolatileno lecturas. En otras palabras, básicamente proporciona 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 ).volatilevolatile
En conjunto, estas garantías conforman volatileuna útil construcción de multihilo en Java . En particular, el algoritmo de bloqueo de doble verificaciónvolatile típico funciona correctamente en Java . [ 11 ]
Versiones antiguas de Java
Antes de la versión 5 de Java, el estándar Java no garantizaba el orden relativo de volatilelas volatilelecturas y escrituras. En otras palabras, volatileno tenía semántica de barrera de memoria "adquirir" y "liberar" . Esto limitaba enormemente su uso como construcción de multihilo . En particular, el algoritmo de bloqueo de doble verificación típico con volatileno funcionaba correctamente.
Cª#
En C# , volatilese asegura de que el código que accede al campo no esté sujeto a optimizaciones que no son seguras para subprocesos y que pueden ser realizadas por el compilador, el CLR o el hardware. Cuando un campo está marcado volatile, se le indica al compilador que genere una "barrera de memoria" o "valla" a su alrededor, lo que impide la reordenación de instrucciones o el almacenamiento en caché vinculados al campo. Al leer un volatilecampo, el compilador genera una valla de adquisición , que impide que otras lecturas y escrituras en el campo se muevan antes de la valla. Al escribir en un volatilecampo, el compilador genera una valla de liberación ; esta valla impide que otras lecturas y escrituras en el campo se muevan después de la valla. [ 12 ]
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. [ 13 ] (Esto excluye las estructuras de valor , así como los tipos primitivos Double, Int64, UInt64y Decimal.)
El uso de la volatilepalabra clave no admite campos que se pasan por referencia o variables locales capturadas ; en estos casos, Thread.VolatileReadse Thread.VolatileWritedebe usar en su lugar. [ 12 ]
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.VolatileReady Thread.VolatileWriteson un superconjunto de las garantías proporcionadas por la volatilepalabra 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 la preceden), VolatileReady VolatileWritegeneran una "valla completa" que impide la reordenación y el almacenamiento en caché de las instrucciones de ese campo en ambas direcciones. [ 12 ] Estos métodos funcionan de la siguiente manera: [ 14 ]
- Este
Thread.VolatileWritemétodo fuerza la escritura del valor en el campo en el momento de la llamada. Además, cualquier carga o almacenamiento anterior en el orden del programa debe ocurrir antes de la llamada,VolatileWritey cualquier carga o almacenamiento posterior debe ocurrir después de la llamada. - Este
Thread.VolatileReadmétodo fuerza la lectura del valor del campo en el momento de la llamada. Además, cualquier carga o almacenamiento anterior en el orden del programa debe ocurrir antes de la llamada,VolatileReady cualquier carga o almacenamiento posterior debe ocurrir después de la llamada.
Los métodos Thread.VolatileReady Thread.VolatileWritegeneran una barrera completa llamando al Thread.MemoryBarriermétodo, que construye una barrera de memoria que funciona en ambas direcciones. Además de las motivaciones para usar una barrera completa dadas anteriormente, un problema potencial con la volatilepalabra clave que se resuelve usando una barrera completa generada por Thread.MemoryBarrieres el siguiente: debido a la naturaleza asimétrica de las medias barreras, un volatilecampo 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. Como las barreras completas son simétricas, esto no es un problema cuando se usa Thread.MemoryBarrier. [ 12 ]
En Fortran
VOLATILEforma parte del estándar Fortran 2003 , [ 15 ] aunque versiones anteriores lo admitían como una extensión. Hacer que todas las variables volatileestén en una función también es útil para encontrar errores relacionados con alias .
entero , volátil :: i ! Cuando no se define como volátil, las siguientes dos líneas de código 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 valoresAl acceder siempre a la memoria de un VOLATILE, el compilador de Fortran no puede reordenar las lecturas o escrituras en volatiles. Esto hace visibles a otros hilos las acciones realizadas en este hilo, y viceversa. [ 16 ]
El uso de VOLATILE reduce e incluso puede impedir la optimización. [ 17 ]
Referencias
- ↑ "Publicación en el comité de estándares de C++" .
- ↑ "Palabra clave volátil en Visual C++" . Microsoft MSDN . 21 de septiembre de 2021.
- ↑ "Documentación del kernel de Linux: por qué no se debe usar la clase de tipo "volatile" . kernel.org .
- ↑ Scott Meyers; Andrei Alexandrescu (2004). "C++ y los peligros del bloqueo de doble verificación" (PDF) . DDJ .
- ↑ Jeremy Andrews (2007). "Linux: superstición volátil" . kerneltrap.org. Archivado del original el 20 de junio de 2010. Consultado el 9 de enero de 2011 .
- ↑ "volatile (C++)" . Microsoft MSDN . 21 de septiembre de 2021.
- ↑ Eide, Eric; Regehr, John (octubre de 2008). "Los archivos volátiles se compilan incorrectamente y qué hacer al respecto" (PDF) . Actas de la octava Conferencia Internacional ACM e IEEE sobre Software Embebido (EMSOFT), Atlanta, Georgia, EE. UU. – vía cs.utah.edu.
- ↑ "Errores volátiles, tres años después: integrados en la academia" . blog.regehr.org . Consultado el 28 de agosto de 2024 .
- ↑ Sección 17.4.4: Orden de sincronización "Especificación del lenguaje Java®, edición Java SE 7" . Oracle Corporation . 2013. Consultado el 12 de mayo de 2013 .
- ↑ "Concurrencia en Java: Entendiendo la palabra clave 'Volatile'" . dzone.com. 8 de marzo de 2021. Archivado del original el 9 de mayo de 2021. Consultado el 9 de mayo de 2021 .
- ↑ Neil Coffey. "Bloqueo doblemente verificado (DCL) y cómo solucionarlo" . Javamex . Consultado el 19 de septiembre de 2009 .
- 1 2 3 4 Albahari, Joseph. "Parte 4: Hilos avanzados" . Hilos en C# . O'Reilly Media. Archivado del original el 12 de diciembre de 2019. Recuperado el 9 de diciembre de 2019 .
{{cite web}}: CS1 maint: bot: estado de la URL original desconocido ( enlace ) - ↑ Richter, Jeffrey (11 de febrero de 2010). «Capítulo 7: Constantes y campos». CLR Via C# . Microsoft Press. 183 págs . ISBN 978-0-7356-2704-8.
- ↑ Richter, Jeffrey (11 de febrero de 2010). «Capítulo 28: Construcciones primitivas de sincronización de subprocesos». CLR Via C# . Microsoft Press. págs. 797-803 . ISBN 978-0-7356-2704-8.
- ↑ "Atributo y declaración VOLATILE" . Cray. Archivado del original el 23/01/2018 . Recuperado el 22/04/2016 .
- ↑ "Matrices volátiles y compartidas en Fortran" . Intel.com .
- ↑ "VOLÁTIL" . Oracle.com .
Enlaces externos
- Manual de referencia de Ada C.6: Control de variables compartidas
- Núcleo de Linux: volatile-considered-harful
- C (lenguaje de programación)
- Control de concurrencia
- Variable (informática)