Supongamos que hay una clase que contiene un campo contador público de int al que pueden acceder múltiples hilos, y este número solo aumentará o disminuirá.
Al añadir este campo, ¿cuál de los siguientes esquemas debería usarse y por qué?
- lock(this.locker) this.counter++;
- Interlocked.Increment(ref this.counter);
- Cambiar el modificador de acceso de contador a volátil público
Peor (ninguna de ellas funciona realmente)
Cambiar el modificador de acceso de contador a volátil público
Este enfoque en realidad no es seguro en absoluto, y la cuestión de Volátil es que múltiples hilos ejecutándose en múltiples CPUs almacenan los datos en búfer y reorganizan las instrucciones ejecutadas.
Si es no volátil, cuando la CPU A aumenta un valor, la CPU B necesita esperar un tiempo para ver el valor incrementado, lo que puede causar problemas.
Si es volátil, asegura que ambas CPUs vean el mismo valor al mismo tiempo. Pero no evita las operaciones de lectura y escritura transversales.
Añadir valor a una variable en realidad requiere tres pasos
1. Lectura, 2. Añadir 3. Escribir
Supongamos que el hilo A lee el valor del contador como 1 y no está listo para aumentar, entonces el hilo B también lee el valor del contador como 1, y ambos hilos comienzan a realizar operaciones incrementales y de escritura. El valor de la ficha final es 2. Esto no es correcto, ambos hilos han realizado una operación de aumento, y el resultado correcto debería ser 3. Así que etiquetarlo como volátil es simplemente inseguro.
Es mejor
lock(this.locker) this.counter++;
Así es seguro (recuerda cerrar con llave todos los sitios donde quieras acceder a este mostrador, por supuesto). Evita que cualquier otro hilo ejecute el código bloqueado. Y también previene el problema de secuenciación de instrucciones multi-CPU mencionado anteriormente. El problema es que el bloqueo es lento en rendimiento, y si usas bloqueo en otros lugares no relacionados, puede bloquear los otros hilos.
Lo mejor
Interlocked.Increment(ref this.counter);
Esto es seguro y muy eficiente. Realiza lectura, aumento y escritura de tres operaciones en un mismo átomo sin ser interrumpido en el centro. Como no afecta a otros códigos, no necesitas recordar cerraduras en otros sitios. Y además es muy rápido (como dice MSDN, en las CPUs actuales suele ser solo una instrucción).
Pero no estoy del todo seguro de si también puede resolver el problema de ordenación de instrucciones de la CPU, o si debe usarse junto con volátil y este incremento.
Suplemento: ¿Qué problemas resuelve bien el volátil?
Como el volátil no puede evitar el multihilo, ¿qué puede hacer? Un buen ejemplo es que tienes dos hilos, uno siempre escribe en una variable, digamos que esta variable es queneLength, y el otro siempre lee datos de esta variable. Si la longitud de la cola no es volátil, el hilo A puede leerse 5 veces, pero el hilo B puede ver datos retrasados, o incluso datos en orden incorrecto. Una solución es usar bloqueo, pero en este caso también puedes usar volátil. Esto asegura que el hilo B siempre vea los datos más recientes escritos por el hilo A, pero esta lógica solo funciona si no lo lees al escribirlo, y si no lo escribes al leerlo. Cuando varios hilos quieren realizar operaciones de lectura, modificación y escritura, necesitas usar Interlock o bloqueo.
|