|
|||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||
|
Взаємне
блокування
В процесі
синхронізації блокування
встановлюється для об'єктів, а не
потоків, тому при використанні різних
об'єктів для блокування різних фрагментів
коди в програмах іноді виникають
вельми нетривіальні помилки. На
жаль, у багатьох випадках
синхронізація по одному об'єкту
просто недопустима, оскільки вона
приведе до дуже частого
блокування потоків. Розглянемо
ситуацію взаємного блокування (deadlock)
в простому вигляді. Уявіть собі
двох програмістів за обіднім
столом. На жаль, на двох у них
тільки один ніж і одна вилка. Якщо
припустити, що для їжі потрібні і
ніж і вилка, можливі дві ситуації:
У багатопотоковій
програмі подібна ситуація
називається взаємним блокуванням. Два
методи синхронізуються по різних
об'єктах. Потік А захоплює
об'єкт 1 і входить у фрагмент
програми, захищений цим
об'єктом. На жаль, для роботи
йому необхідний доступ до коду,
захищеного іншим блоком Sync Lock з
іншим об'єктом синхронізації. Але
перш, ніж він встигає увійти до
фрагмента, що синхронізується
іншим об'єктом, в нього входить потік В
і захоплює цей об'єкт.
Тепер потік А не може увійти до
другого фрагмента, потік В не може увійти
до першого фрагмента, і обидва
потоки приречено на нескінченне
очікування. Жоден потік не може продовжити
роботу, оскільки необхідний для
цього об'єкт так і не буде
звільнений.
Нижче приведена
реалізація тільки що описаної
ситуації взаємного блокування.
Після короткого обговорення найбільш
принципових моментів ми покажемо,
як пізнати ситуацію взаємного
блокування у вікні потоків: 1 Option
Strict On 2 Imports System.Threading 3 Module
Modulel 4 Sub Main() 5 Dim Tom As New
Programmer( "Tom") 6 Dim Bob As New
Programmer( "Bob") 7 Dim athreadstart
As New Threadstart(Addressof Tom.Eat) 8 Dim athread As New
Thread(athreadstart) 9
athread.Name=
"Tom" 10 Dim bthreadstart
As New Threadstarttaddressof Bob.Eat) 11 Dim bthread As
New Thread(bthreadstart) 12
bthread.Name =
"Bob" 13 athread.Start() 14 bthread.Start() 15 End
Sub 16 End
Module 17
Public Class Fork 18
Private Shared mforkavaitable As Boolean = True 19
Private Shared mowner As String = "Nobody" 20 Private Readonly
Property Ownsutensil() As String 21
Get 22
Return mowner 23 End
Get 24 End
Property 25 Public Sub
Grabforktbyval а As Programmer) 26 Console.Writel_ine(Thread.CurrentThread.Name &_ "trying to grab
the fork.") 27 Console.WriteLine(Me.OwnsUtensil
& "has the fork.") . . 28 Monitor.Enter(Me)
'Synclock (afork)' 29 If
mforkavailable Then 30 а.HasFork = True 31 mowner = а.MyName 32
mforkavailable = False 33 Console.WriteLine(а.MyName&"just
got the fork.waiting") 34
Try Thread.Sleep(100) Catch e As Exception Console.WriteLine (e.StackTrace) End
Try 35 End
If 36 Monitor.Exit(Me) End
Synclock 37 End
Sub 38 End
Class 39
Public Class Knife 40
Private Shared mknifeavailable As Boolean = True 41
Private Shared mowner As String ="Nobody" 42 Private Readonly
Property Ownsutensi1() As String 43
Get 44
Return mowner 45 End
Get 46 End
Property 47 Public Sub
Grabknifetbyval а As Programmer) 48 Console.WriteLine(Thread.CurrentThread.Name & _ "trying to grab
the knife.") 49 Console.WriteLine(Me.OwnsUtensil
& "has the knife.") 50 Monitor.Enter(Me)
'Synclock (aknife)' 51 If
mknifeavailable Then 52
mknifeavailable = False 53 а.HasKnife = True 54 mowner = а.MyName 55 Console.WriteLine(а.MyName&"just
got the knife.waiting") 56
Try Thread.Sleep(100) Catch e As Exception Console.WriteLine (e.StackTrace) End
Try 57 End
If 58 Monitor.Exit(Me) 59 End
Sub 60 End
Class 61
Public Class Programmer 62
Private mname As String 63
Private Shared mfork As Fork 64
Private Shared mknife As Knife 65
Private mhasknife As Boolean 66
Private mhasfork As Boolean 67 Shared Sub New() 68 mfork = New Fork() 69 mknife = New
Knife() 70 End
Sub 71 Public Sub New(Byval
thename As String) 72 mname
= thename 73 End
Sub 74 Public Readonly
Property Myname() As String 75 Get
76
Return mname 77 End
Get 78 End
Property 79 Public Property
Hasknife() As Boolean 80
Get 81
Return mhasknife 82 End
Get 83 Set(Byval Value
As Boolean) 84
mhasknife = Value 85 End
Set 86 End
Property 87 Public Property
Hasfork() As Boolean 88
Get 89
Return mhasfork 90 End
Get 91 Set(Byval Value
As Boolean) 92
mhasfork = Value 93 End
Set 94 End
Property 95 Public Sub Eat() 96 Do Until Me.HasKnife
And Me.HasFork 97 Console.Writeline(Thread.CurrentThread.Name&"is
in the thread.") 98 If Rnd() < 0.5
Then 99 mfork.GrabFork(Me) 100
Else 101 mknife.GrabKnife(Me) 102 End
If 103
Loop 104 Msgbox(Me.MyName
& "can eat!") 105 mknife = New
Knife() 106 mfork= New Fork() 107 End
Sub 108 End Class Основна процедура
Main (рядки 4-16) створює два екземпляри
класу Programmer і потім запускає
два потоки для виконання
критичного методу Eat класу
Programmer (рядки 95-108), описаного
нижче. Процедура Main задає
імена потоків і зануськаєт їх; ймовірно,
що все відбувається зрозуміло
і без коментарів. Цікавіше
виглядає код класу Fork (рядки 17-38) (аналогічний
клас Knife визначається в рядках 39-60).
У рядках 18 і 19 задаються значення
загальних полів, по яких можна
дізнатися, чи доступна в даний момент
вилка, і якщо немає — хто нею
користується. ReadOnly-свойство Ownutensi1 (рядки
20-24) призначена для простої
передачі інформації. Центральне
місце в класі Fork займає метод «захоплення
вилки» Grabfork, визначуваний в рядках
25-27.
Все
це продовжується до безкінечності
— перед нами типова ситуація
взаємного блокування (спробуйте
запустити програму, і ви переконаєтеся
в тому, що поїсти так нікому і не
вдається).
Мал. 10.7. Аналіз
взаємного блокування у вікні потоків Отже, якщо прибрати виклик Rnd в рядку 98 і замінити
його фрагментом mfork.GrabFork(Me) mknife.GrabKnife(Me) взаємне
блокування зникає!
|
|
|||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||