significado - c#- Uso de palabras clave volátiles vs bloqueo
palabras reservadas en c# y para que sirven (5)
He usado volatile donde no estoy seguro de que sea necesario. Estaba bastante seguro de que una cerradura sería una exageración en mi situación. Leer este hilo (comentario de Eric Lippert) me pone ansioso por mi uso de volatile: ¿ Cuándo debería usarse la palabra clave volatile en c #?
Utilicé volatile porque mi variable se usa en un contexto de multiproceso donde se puede acceder / modificar a esta variable simultáneamente, pero donde puedo perder una adición sin ningún daño (consulte el código).
Agregué "volatile" para asegurarme de que no se produzca una alineación incorrecta: leer solo 32 bits de la variable y los otros 32 bits en otra búsqueda que puede romperse en 2 por una escritura en el medio desde otro hilo.
¿Mi suposición anterior (declaración anterior) puede realmente pasar de no? De lo contrario, el uso "volátil" sigue siendo necesario (las modificaciones de las propiedades de la opción podrían ocurrir en cualquier subproceso).
Después de leer las 2 primeras respuestas. Me gustaría insistir en el hecho de que la forma en que se escribe el código, no es importante si debido a la concurrencia omitimos un incremento (queremos incrementar desde 2 subprocesos pero el resultado solo se incrementa en uno debido a la concurrencia) si al menos La variable ''_actualVersion'' se incrementa.
Como referencia, esta es la parte del código donde lo estoy usando. Es para informar sobre la acción de guardar (escribir en el disco) solo mientras la aplicación está inactiva.
public abstract class OptionsBase : NotifyPropertyChangedBase
{
private string _path;
volatile private int _savedVersion = 0;
volatile private int _actualVersion = 0;
// ******************************************************************
void OptionsBase_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
_actualVersion++;
Application.Current.Dispatcher.BeginInvoke(new Action(InternalSave), DispatcherPriority.ApplicationIdle);
}
// ******************************************************************
private void InternalSave()
{
if (_actualVersion != _savedVersion)
{
_savedVersion = _actualVersion;
Save();
}
}
// ******************************************************************
/// <summary>
/// Save Options
/// </summary>
private void Save()
{
using (XmlTextWriter writer = new XmlTextWriter(_path, null))
{
writer.Formatting = Formatting.Indented;
XmlSerializer x = new XmlSerializer(this.GetType());
x.Serialize(writer, this);
writer.Close();
}
}
He usado volatile donde no estoy seguro de que sea necesario.
Permítanme ser muy claro en este punto:
Si no está 100% seguro de lo que significa volátil en C #, no lo use . Es una herramienta afilada que debe ser utilizada solo por expertos. Si no puede describir todas las posibles reordenaciones de accesos de memoria permitidas por una arquitectura de modelo de memoria débil cuando dos subprocesos están leyendo y escribiendo dos campos volátiles diferentes, entonces no sabe lo suficiente como para usarlo de forma segura y cometerá errores, como lo ha hecho. Hecho aquí, y escribe un programa que es extremadamente frágil.
Estaba bastante seguro de que una cerradura sería una exageración en mi situación
En primer lugar, la mejor solución es simplemente no ir allí . Si no escribe código multiproceso que intenta compartir memoria, entonces no tiene que preocuparse por el bloqueo, lo cual es difícil de corregir.
Si debe escribir código de multiproceso que comparta memoria, entonces la mejor práctica es usar siempre bloqueos. Las cerraduras casi nunca son excesivas. El precio de un bloqueo incontenido es del orden de diez nanosegundos. ¿Realmente me estás diciendo que diez nanosegundos adicionales harán una diferencia para tu usuario? Si es así, entonces tienes un programa muy, muy rápido y un usuario con estándares inusualmente altos.
El precio de un bloqueo en disputa es, por supuesto, arbitrariamente alto si el código dentro del bloqueo es costoso. No haga un trabajo costoso dentro de una cerradura , por lo que la probabilidad de contención es baja.
Solo cuando tiene un problema de rendimiento demostrado con bloqueos que no se pueden resolver eliminando la contención, debería comenzar a considerar una solución de bloqueo bajo.
Agregué "volatile" para asegurarme de que no haya desalineación: leyendo solo 32 bits de la variable y los otros 32 bits en otra búsqueda que puede dividirse en dos por una escritura en el medio desde otro hilo.
Esta oración me dice que debes dejar de escribir código de multiproceso ahora. El código multiproceso, particularmente el código de bloqueo bajo, es solo para expertos. Debe comprender cómo funciona realmente el sistema antes de comenzar a escribir código de multiproceso nuevamente. Consigue un buen libro sobre el tema y estudia mucho.
Tu oración no tiene sentido porque:
En primer lugar, los enteros ya son solo 32 bits.
En segundo lugar, la especificación garantiza que los accesos int son atómicos. Si quieres atomicidad, ya la tienes.
Tercero, sí, es cierto que los accesos volátiles siempre son atómicos, pero eso no es porque C # convierte a todos los accesos volátiles en accesos atómicos. Más bien, C # hace que sea ilegal poner volátil en un campo a menos que el campo ya sea atómico.
Cuarto, el propósito de volatile es evitar que el compilador, el jitter y la CPU de C # realicen ciertas optimizaciones que cambiarían el significado de su programa en un modelo de memoria débil. El volátil en particular no hace a ++ atómico. (Trabajo para una compañía que fabrica analizadores estáticos; usaré su código como un caso de prueba para nuestro verificador de "operación no atómica incorrecta en campo volátil". Para mí es muy útil obtener un código del mundo real lleno de errores realistas; queremos asegurarnos de que realmente estamos encontrando los errores que las personas escriben, así que gracias por publicar esto.)
Mirando su código real: volatile es, como lo señaló Hans, totalmente inadecuado para hacer que su código sea correcto. Lo mejor que puedo hacer es lo que dije antes: no permitas que se llame a estos métodos en ningún hilo que no sea el hilo principal . Que la contra lógica sea incorrecta debería ser la menor de tus preocupaciones. ¿Qué hace que el subproceso de serialización sea seguro si el código de otro subproceso está modificando los campos del objeto mientras se está serializando? Ese es el problema que debe preocuparte primero.
Con respecto a su declaración de si una variable podría dividirse en dos recuperaciones de 32 bits al usar volatile, esto podría ser una posibilidad si estuviera usando algo más grande que Int32.
Entonces, mientras esté utilizando Int32, no tendrá ningún problema con lo que está diciendo.
Sin embargo, como ha leído en el enlace sugerido, volatile solo le ofrece garantías débiles y preferiría los bloqueos para estar seguro, ya que las máquinas de hoy tienen más de una CPU y volatile no garantiza que otra CPU no se active. y hacer algo inesperado.
EDITAR
¿Has considerado usar Interlocked.Increment?
De acuerdo con la excelente publicación de Joe Albahari en temas y bloqueos, que proviene de su excelente libro C # 5.0 En pocas palabras, dice que incluso cuando se usa la palabra clave volátil, se puede reordenar una declaración de escritura seguida de una lectura.
Más abajo, dice que la documentación de MSDN sobre este tema es incorrecta y sugiere que hay un caso sólido para evitar la palabra clave volátil por completo . Señala que incluso si entiendes las sutilezas involucradas, ¿lo entenderán también otros desarrolladores en el futuro?
Por lo tanto, usar un candado no solo es más "correcto", sino que también es más comprensible, y ofrece la facilidad de agregar una nueva característica que agrega múltiples declaraciones de actualización al código de una manera atómica, lo que no puede hacer ni una clase de valla ni una clase de valla. , es muy rápido, y es mucho más fácil de mantener, ya que hay menos posibilidades de introducir un error sutil por parte de desarrolladores menos experimentados en el futuro.
La comparación y la asignación en su método InternalSave () no serían seguras para subprocesos con o sin la palabra clave volátil. Si desea evitar el uso de bloqueos, puede usar los métodos CompareExchange () y Increment () en su código de la clase de Interlocked del marco.
La volatilidad es lamentablemente inadecuada para hacer que este código sea seguro. Puede usar el bloqueo de bajo nivel con Interlocked.Increment () y Interlocked.CompareExchange () pero hay muy pocas razones para suponer que Save () es seguro para subprocesos. Parece que intenta guardar un objeto que está siendo modificado por un subproceso de trabajo.
El uso del bloqueo está muy bien indicado aquí, no solo para proteger los números de versión sino también para evitar que el objeto cambie mientras se está serializando. Las salvaciones dañadas que obtendrá al no hacer esto son tan poco frecuentes como para tener una inyección y un problema de depuración.