Operaciones de bloqueo
Actualización: noviembre 2007
La clase Interlocked proporciona métodos que sincronizan el acceso a una variable compartida por varios subprocesos. Los subprocesos de diferentes procesos pueden utilizar este mecanismo si la variable está en la memoria compartida. Las operaciones de bloqueo son atómicas, es decir, el conjunto de la operación constituye una unidad que ninguna otra operación de bloqueo puede interrumpir en la misma variable. Esto último tiene importancia en sistemas operativos con subprocesamiento múltiple preferente, donde se puede suspender un subproceso después de cargar un valor desde una dirección de memoria, pero antes de tener oportunidad de alterarlo y almacenarlo.
La clase Interlocked proporciona las siguientes operaciones:
En la versión 2.0 de .NET Framework, el método Add agrega un valor entero a una variable y devuelve el nuevo valor de la variable.
En la versión 2.0 de .NET Framework, el método Read lee un valor entero de 64 bits como una operación atómica. Esto resulta útil en sistemas operativos de 32 bits, donde leer un entero de 64 bits no constituye habitualmente una operación atómica.
Los métodos Increment y Decrement incrementan o decrementan una variable y devuelven el valor resultante.
El método Exchange realiza un intercambio atómico del valor en la variable especificada, devolviendo dicho valor y reemplazándolo por un valor nuevo. En la versión 2.0 de .NET Framework, puede utilizarse una sobrecarga genérica de este método para realizar este intercambio en una variable con cualquier tipo de referencia. Vea Exchange<T>(T%, T).
El método CompareExchange también intercambia dos valores, pero en función del resultado de una comparación. En la versión 2.0 de .NET Framework, puede utilizarse una sobrecarga genérica de este método para realizar este intercambio en una variable con cualquier tipo de referencia. Vea CompareExchange<T>(T%, T, T).
En los procesadores modernos, los métodos de la clase Interlocked a menudo pueden implementarse mediante una única instrucción. Así, proporcionan una sincronización con un rendimiento muy elevado y pueden utilizarse para generar mecanismos de sincronización de nivel superior, como bloqueos circulares.
Para obtener un ejemplo que utiliza las clases Monitor y Interlocked en combinación, vea Monitores.
Ejemplo de CompareExchange
El método CompareExchange se puede utilizar para proteger cálculos más complicados que un simple incremento y decremento. En el siguiente ejemplo se muestra un método seguro para subprocesos que se agrega a un total actualizado almacenado como un número de punto flotante. (En el caso de los números enteros, el método Add constituye una solución más sencilla.) Para obtener ejemplos de código completos, vea las sobrecargas de CompareExchange que toma argumentos de punto flotante de precisión simple y doble (CompareExchange(Single%, Single, Single) y CompareExchange(Double%, Double, Double)).
Imports System.Threading
Public Class ThreadSafe
' Field totalValue contains a running total that can be updated
' by multiple threads. It must be protected from unsynchronized
' access.
Private totalValue As Double = 0.0
' The Total property returns the running total.
Public ReadOnly Property Total As Double
Get
Return totalValue
End Get
End Property
' AddToTotal safely adds a value to the running total.
Public Function AddToTotal(ByVal addend As Double) As Double
Dim initialValue, computedValue As Double
Do
' Save the current running total in a local variable.
initialValue = totalValue
' Add the new value to the running total.
computedValue = initialValue + addend
' CompareExchange compares totalValue to initialValue. If
' they are not equal, then another thread has updated the
' running total since this loop started. CompareExchange
' does not update totalValue. CompareExchange returns the
' contents of totalValue, which do not equal initialValue,
' so the loop executes again.
Loop While initialValue <> Interlocked.CompareExchange( _
totalValue, computedValue, initialValue)
' If no other thread updated the running total, then
' totalValue and initialValue are equal when CompareExchange
' compares them, and computedValue is stored in totalValue.
' CompareExchange returns the value that was in totalValue
' before the update, which is equal to initialValue, so the
' loop ends.
' The function returns computedValue, not totalValue, because
' totalValue could be changed by another thread between
' the time the loop ends and the function returns.
Return computedValue
End Function
End Class
using System.Threading;
public class ThreadSafe {
// totalValue contains a running total that can be updated
// by multiple threads. It must be protected from unsynchronized
// access.
private double totalValue = 0;
// The Total property returns the running total.
public double Total {
get { return totalValue; }
}
// AddToTotal safely adds a value to the running total.
public double AddToTotal(double addend) {
double initialValue, computedValue;
do {
// Save the current running total in a local variable.
initialValue = totalValue;
// Add the new value to the running total.
computedValue = initialValue + addend;
// CompareExchange compares totalValue to initialValue. If
// they are not equal, then another thread has updated the
// running total since this loop started. CompareExchange
// does not update totalValue. CompareExchange returns the
// contents of totalValue, which do not equal initialValue,
// so the loop executes again.
} while (initialValue != Interlocked.CompareExchange(
ref totalValue, computedValue, initialValue));
// If no other thread updated the running total, then
// totalValue and initialValue are equal when CompareExchange
// compares them, and computedValue is stored in totalValue.
// CompareExchange returns the value that was in totalValue
// before the update, which is equal to initialValue, so the
// loop ends.
// The function returns computedValue, not totalValue, because
// totalValue could be changed by another thread between
// the time the loop ends and the function returns.
return computedValue;
}
}
Sobrecargas sin tipo de Exchange y CompareExchange
Los métodos Exchange y CompareExchange disponen de sobrecargas que aceptan argumentos de tipo Object. El primer argumento de cada una de estas sobrecargas es ref Object (ByRef … As Object en Visual Basic) y la seguridad de tipos requiere que se pase la variable a este argumento para que sea estrictamente de tipo Object; no se puede transformar simplemente el primer argumento a tipo Object cuando se realiza la llamada a estos métodos.
Nota: |
---|
En la versión 2.0 de .NET Framework, utilice las sobrecargas genéricas de los métodos Exchange y CompareExchange para intercambiar variables con establecimiento inflexible de tipos. |
En el siguiente ejemplo de código se muestra una propiedad de tipo ClassA que sólo puede establecerse una vez, ya que debería implementarse en la versión 1.0 o 1.1 de .NET Framework.
Public Class ClassB
' The private field that stores the value for the
' ClassA property is intialized to Nothing. It is set
' once, from any of several threads. The field must
' be of type Object, so that CompareExchange can be
' used to assign the value. If the field is used
' within the body of class Test, it must be cast to
' type ClassA.
Private classAValue As [Object] = Nothing
' This property can be set once to an instance of
' ClassA. Attempts to set it again cause an
' exception to be thrown.
Public Property ClassA() As ClassA
Set
' CompareExchange compares the value in classAValue
' to Nothing. The new value assigned to the ClassA
' property, which is in the special variable 'value',
' is placed in classAValue only if classAValue is
' equal to Nothing.
If Not (Nothing Is Interlocked.CompareExchange(classAValue, _
CType(value, [Object]), Nothing)) Then
' CompareExchange returns the original value of
' classAValue; if it was not Nothing, then a value
' was already assigned, and CompareExchange did not
' replace the original value. Throw an exception to
' indicate that an error occurred.
Throw New ApplicationException("ClassA was already set.")
End If
End Set
Get
Return CType(classAValue, ClassA)
End Get
End Property
End Class ' ClassB
public class ClassB {
// The private field that stores the value for the
// ClassA property is intialized to null. It is set
// once, from any of several threads. The field must
// be of type Object, so that CompareExchange can be
// used to assign the value. If the field is used
// within the body of class Test, it must be cast to
// type ClassA.
private Object classAValue = null;
// This property can be set once to an instance of
// ClassA. Attempts to set it again cause an
// exception to be thrown.
public ClassA ClassA {
set {
// CompareExchange compares the value in classAValue
// to null. The new value assigned to the ClassA
// property, which is in the special variable 'value',
// is placed in classAValue only if classAValue is
// equal to null.
if (null != Interlocked.CompareExchange(ref classAValue,
(Object) value, null)) {
// CompareExchange returns the original value of
// classAValue; if it is not null, then a value
// was already assigned, and CompareExchange did not
// replace the original value. Throw an exception to
// indicate that an error occurred.
throw new ApplicationException("ClassA was already set.");
}
}
get {
return (ClassA) classAValue;
}
}
}