Решение проблемы: синхронизация

В предыдущей программе возникает ситуация, когда результат работы программы зависит от порядка выполнения потоков. Чтобы избавиться от нее, необходимо убедиться в том, что команды типа

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.