Главная
опасность (общие данные)
До настоящего
момента рассматривался
единственный безопасный случай
использования потоков — наши
потоки не изменяли общих данных. Если
разрешить изменение общих данных,
потенциальные ошибки начинают
плодиться в геометрической
прогрессии и избавить от них
программу становится гораздо
труднее. С другой стороны, если
запретить модификацию общих данных
разными потоками, многопоточное
программирование .NET практически не
будет отличаться от ограниченных
возможностей VB6.
Вашему вниманию
предлагается небольшая программа,
которая демонстрирует возникающие
проблемы, не углубляясь в излишние
подробности. В этой программе
моделируется дом, в каждой комнате
которого установлен термостат.
Если температура на 5 и более
градусов по Фаренгейту (около 2,77
градусов по Цельсию) меньше
положенной, мы приказываем системе
отопления повысить температуру на 5
градусов; в противном случае
температура повышается только на 1
градус. Если текущая температура
больше либо равна заданной,
изменение не производится.
Регулировка температуры в каждой
комнате осуществляется отдельным
потоком с 200-миллисекундной
задержкой. Основная работа
выполняется следующим фрагментом:
If mHouse.HouseTemp
< mHouse.MAX_TEMP = 5 Then Try
Thread.Sleep(200)
Catch tie As
ThreadlnterruptedException
' Пассивное ожидание было прервано
Catch e As Exception
' Другие
исключения End Try
mHouse.HouseTemp +-
5 ' И т.д.
Ниже приведен
полный исходный текст программы.
Результат показан на рис. 10.6:
температура в доме достигла 105
градусов по Фаренгейту (40,5 градуса
по Цельсию)!
1 Option Strict On
2 Imports System.Threading
3 Module Modulel
4 Sub Main()
5 Dim myHouse As New
House(l0)
6 Console. ReadLine()
7 End Sub
8 End Module
9 Public Class House
10 Public Const
MAX_TEMP As Integer = 75
11 Private mCurTemp
As Integer = 55
12 Private mRooms()
As Room
13 Public Sub New(ByVal
numOfRooms As Integer)
14 ReDim mRooms(numOfRooms
= 1)
15 Dim i As Integer
16 Dim aThreadStart
As Threading.ThreadStart
17 Dim aThread As
Thread
18 For i = 0 To
numOfRooms -1
19 Try
20 mRooms(i)=NewRoom(Me,
mCurTemp,CStr(i) &"throom")
21 aThreadStart - New ThreadStart(AddressOf _
mRooms(i).CheckTempInRoom)
22 aThread =New
Thread(aThreadStart)
23 aThread.Start()
24 Catch E As
Exception
25 Console.WriteLine(E.StackTrace)
26 End Try
27 Next
28 End Sub
29 Public Property
HouseTemp()As Integer
30 . Get
31 Return mCurTemp
32 End Get
33 Set(ByVal Value
As Integer)
34 mCurTemp = Value
35 End Set
36 End Property
37 End Class
38 Public Class Room
39 Private mCurTemp
As Integer
40 Private mName As
String
41 Private mHouse As
House
42 Public Sub New(ByVal theHouse As House,
ByVal temp As
Integer, ByVal roomName As String)
43 mHouse = theHouse
44 mCurTemp = temp
45 mName = roomName
46 End Sub
47 Public Sub
CheckTempInRoom()
48 ChangeTemperature()
49 End Sub
50 Private Sub
ChangeTemperature()
51 Try
52 If mHouse.HouseTemp
< mHouse.MAX_TEMP - 5 Then
53 Thread.Sleep(200)
54 mHouse.HouseTemp
+- 5
55 Console.WriteLine("Am
in " & Me.mName & _
56 ".Current
temperature is "&mHouse.HouseTemp)
57 . Elself mHouse.HouseTemp
< mHouse.MAX_TEMP Then
58 Thread.Sleep(200)
59 mHouse.HouseTemp
+= 1
60 Console.WriteLine("Am
in " & Me.mName & _
61 ".Current
temperature is " & mHouse.HouseTemp)
62 Else
63 Console.WriteLine("Am
in " & Me.mName & _
64 ".Current
temperature is " & mHouse.HouseTemp)
65 ' Ничего
не делать, температура нормальная
66 End If
67 Catch tae As
ThreadlnterruptedException
68 '
Пассивное ожидание было прервано
69 Catch e As
Exception
70 ' Другие
исключения
71 End Try
72 End Sub
73 End Class
Рис. 10.6. Проблемы
многопоточности
В процедуре Sub
Main (строки 4-7) создается «дом» с
десятью «комнатами». Класс House
устанавливает максимальную
температуру 75 градусов по
Фаренгейту (около 24 градусов по
Цельсию). В строках 13-28 определяется
довольно сложный конструктор дома.
Ключевыми для понимания программы
являются строки 18-27. Строка 20
создает очередной объект комнаты,
при этом конструктору передается
ссылка на объект дома, чтобы объект
комнаты при необходимости мог к
нему обратиться. Строки 21-23
запускают десять потоков для
регулировки температуры в каждой
комнате. Класс Room определяется в
строках 38-73. Ссылка на объект House coxpaняется
в переменной mHouse в конструкторе
класса Room (строка 43). Код проверки и
регулировки температуры (строки 50-66)
выглядит просто и естественно, но
как вы вскоре убедитесь, это
впечатление обманчиво! Обратите
внимание на то, что этот код
заключен в блок Try-Catch, поскольку в
программе используется метод Sleep.
Вряд ли кто-нибудь
согласится жить при температуре в
105 градусов по Фаренгейту (40,5 24
градусов по Цельсию). Что же
произошло? Проблема связана со
следующей строкой:
If mHouse.HouseTemp
< mHouse.MAX_TEMP - 5 Then
А происходит
следующее: сначала температуру
проверяет поток 1. Он видит, что
температура слишком низка, и
поднимает ее на 5 градусов. К
сожалению, перед повышением
температуры поток 1 прерывается и
управление передаётся поток 2.
Поток 2 проверяет ту же самую
переменную, которая еще не была
изменена потоком 1. Таким образом,
поток 2 тоже готовится поднять
температуру на 5 градусов, но
сделать этого не успевает и тоже
переходит в состояние ожидания.
Процесс продолжается до тех пор,
пока поток 1 не активизируется и не
перейдет к следующей команде —
повышению температуры на 5 градусов.
Повышение повторяется при
активизации всех 10 потоков, и
жильцам дома придется плохо.