Блокируемые операции
Обновлен: Ноябрь 2007
Класс Interlocked содержит методы для синхронизации доступа к переменной, которая совместно используется несколькими потоками. Если переменная находится в общей памяти, этот механизм может использоваться потоками различных процессов. Блокируемые операции являются атомарными. Это значит, что вся операция является неделимой и не может прерываться другой блокируемой операцией с той же переменной. Это важно в операционных системах, использующих многопоточность с вытеснением, где поток может быть приостановлен после того, как он загрузит значение из адреса памяти, но до того, как он получит возможность изменить это значение и сохранить в памяти.
Класс Interlocked выполняет следующие операции.
В платформе .NET Framework версии 2.0 метод Add добавляет к переменной целочисленное значение и возвращает новое значение переменной.
В платформе .NET Framework версии 2.0, метод Read считывает 64-разрядное целочисленное значение в ходе атомарной операции. Это полезно в 32-разрядных операционных системах, где считывание 64-разрядного целого числа обычно не является атомарной операцией.
Методы Increment и Decrement увеличивают или уменьшают переменную и возвращают результирующее значение.
Метод Exchange выполняет атомарный обмен значений для указанной переменной, возвращая текущее значение и заменяя его новым значением. Универсальная перегружаемая версия этого метода в платформе .NET Framework версии 2.0 может использоваться для обмена значений в переменной любого ссылочного типа. См. описание метода Exchange<T>(T%, T).
Метод CompareExchange также обменивает два значения, но делает это в зависимости от результата сравнения. Универсальная перегружаемая версия этого метода в платформе .NET Framework версии 2.0 может использоваться для обмена значений в переменной любого ссылочного типа. См. описание метода CompareExchange<T>(T%, T, T).
Современные процессоры часто позволяют реализовать методы класса Interlocked одной инструкцией. Поэтому они обеспечивают очень высокую производительность синхронизации и позволяют построить механизмы синхронизации более высокого уровня, такие как спин-блокировка.
Пример, использующий сочетание классов Monitor и Interlocked, см. в разделе Мониторы.
Пример CompareExchange
Метод CompareExchange служит для защиты более сложных вычислений, чем простое увеличение и уменьшение значений. В следующем примере показан потокобезопасный метод, который добавляет значение к промежуточной сумме, хранящейся в виде числа с плавающей запятой. (Для целочисленных значений более простым решением будет метод Add.) Полные примеры кода см. в описании перегруженных версий метода CompareExchange, которые принимают аргументы с плавающей запятой одинарной и двойной точности (CompareExchange(Single%, Single, Single) и 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;
}
}
Нетипизированные перегруженные версии методов Exchange и CompareExchange
Методы Exchange и CompareExchange имеют перегруженные версии, принимающие аргументы типа Object. Первый аргумент в каждой из этих версий имеет тип ref Object (ByRef … As Object в Visual Basic), и для безопасности типов необходима строгая типизация переменной, передаваемой этому аргументу, по типу Object. При вызове этих методов нельзя просто привести первый аргумент к типу Object.
Примечание. |
---|
В платформе .NET Framework версии 2.0 для обмена строго типизированными переменными используются универсальные перегружаемые версии методов Exchange и CompareExchange. |
В следующем примере кода показано свойство типа ClassA, которое может задаваться только один раз, и его реализация в платформе .NET Framework версии 1.0 или 1.1.
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;
}
}
}