Решение
проблемы: синхронизация
В предыдущей
программе возникает ситуация,
когда результат работы программы
зависит от порядка выполнения
потоков. Чтобы избавиться от нее,
необходимо убедиться в том, что
команды типа
If mHouse.HouseTemp
< mHouse.MAX_TEMP - 5 Then...
полностью
отрабатываются активным потоком до
того, как он будет прерван. Это
свойство называется атомарностыд
— блок кода должен выполняться
каждым потоком без прерывания, как
атомарная единица. Группа команд,
объединенных в атомарный блок, не
может быть прервана планировщиком
потоков до ее завершения. В любом
многопоточном языке
программирования существуют свои
способы обеспечения атомарности. В
VB .NET проще всего воспользоваться
командой SyncLock, при вызове которой
передается объектная переменная.
Внесите в процедуру ChangeTemperature из
предыдущего примера небольшие
изменения, и программа заработает
нормально:
Private Sub
ChangeTemperature() SyncLock (mHouse)
Try
If mHouse.HouseTemp < mHouse.MAXJTEMP -5 Then
Thread.Sleep(200)
mHouse.HouseTemp += 5
Console.WriteLine("Am
in " & Me.mName & _
".Current temperature is " & mHouse.HouseTemp)
Elself
mHouse.HouseTemp < mHouse. MAX_TEMP Then
Thread.Sleep(200)
mHouse.HouseTemp += 1
Console.WriteLine("Am
in " & Me.mName &_ ".Current temperature is
" & mHouse.HomeTemp) Else
Console.WriteLineC'Am
in " & Me.mName & _ ".Current temperature is
" & mHouse.HouseTemp)
' Ничего не делать, температура нормальная
End If Catch tie As
ThreadlnterruptedException
' Пассивное
ожидание было прервано Catch e As Exception
' Другие исключения
End Try
End SyncLock
End Sub
Код блока SyncLock
выполняется атомарно. Доступ к нему
со стороны всех остальных потоков
будет закрыт, пока первый поток не
снимет блокировку командой End SyncLock.
Если поток в синхронизируемом
блоке переходит в состояние
пассивного ожидания, блокировка
сохраняется вплоть до прерывания
или возобновления работы потока.
Правильное
использование команды SyncLock
обеспечивает потоковую
безопасность вашей программы. К
сожалению, злоупотребление SyncLock
отрицательно сказывается на
быстродействии. Синхронизация кода
в многопоточной программе
уменьшает скорость ее работы в
несколько раз. Синхронизируйте
лишь самый необходимый код и
снимайте блокировку как можно
скорее.
Базовые
классы коллекций небезопасны в
многопоточных приложениях, но в .NET
Framework входят поточно-безопасные
версии большинства классов
коллекций. В этих классах код
потенциально опасных методов
заключается в блоки SyncLock. Поточно-безопасные
версии классов коллекций следует
использовать в многопоточных
программах везде, где возникает
угроза целостности данных.
Остается
упомянуть о том, что при помощи
команды SyncLock легко реализуются
условные переменные. Для этого
потребуется лишь синхронизировать
запись в общее логическое свойство,
доступное для чтения и записи, как
это сделано в следующем фрагменте:
Public Class
ConditionVariable
Private Shared locker As Object= New Object()
Private Shared mOK As Boolean Shared
Property TheConditionVariable()As Boolean
Get
Return mOK
End Get
Set(ByVal Value As
Boolean) SyncLock (locker)
mOK= Value
End SyncLock
End Set
End Property
End Class
Команда
SyncLock и класс Monitor
Использование
команды SyncLock связано с некоторыми
тонкостями, не проявившимися в
приведенных выше простых примерах.
Так, очень важную роль играет выбор
объекта синхронизации. Попробуйте
запустить предыдущую программу с
командой SyncLock(Me) вместо SyncLock(mHouse).
Температура снова поднимается выше
пороговой величины!
Помните, что
команда SyncLock производит
синхронизацию по объекту, переданному
в качестве параметра, а не по
фрагменту кода. Параметр SyncLock
играет роль двери для обращения к
синхронизируемому фрагменту из
других потоков. Команда SyncLock(Me)
фактически открывает несколько
разных «дверей», а ведь именно
этого вы и пытались избежать при
помощи синхронизации. Мораль:
Для защиты
общих данных в многопоточном
приложении команда SyncLock должна
синхронизироваться по одному
объекту.
Поскольку
синхронизация связана с конкретным
объектом, в некоторых ситуациях
возможна непреднамеренная
блокировка других фрагментов.
Допустим, у вас имеются два
синхронизированных метода first и
second, причем оба метода
синхронизируются по объекту bigLock.
Когда поток 1 входит в метод first и
захватывает bigLock, ни один поток не
сможет войти в метод second, потому что
доступ к нему уже ограничен потоком
1!
Функциональность
команды SyncLock можно рассматривать
как подмножество функциональности
класса Monitor. Класс Monitor обладает
расширенными возможностями
настройки, и с его помощью можно
решать нетривиальные задачи
синхронизации. Команда SyncLock
является приближенным аналогом
методов Enter и Exi t класса Moni tor:
Try
Monitor.Enter(theObject)
Finally
Monitor.Exit(theObject)
End Try
Для
некоторых стандартных операций (увеличение/уменьшение
переменной, обмен содержимого двух
переменных) в .NET Framework предусмотрен
класс Interlocked, методы которого
выполняют эти операции на
атомарном уровне. С использованием
класса Interlocked данные операции
выполняются значительно быстрее,
нежели при помощи команды SyncLock.