:: Меню ::

Головна
  • Про сайт
  • Введення
  • Середовище програмування VB .NET: Visual Studio .NET
  • Вирази, оператори і передача управління
  • Класи і об'єкти
  •  Спадкоємство і інтерфейси
  • Обробка подій і делегати
  • Обробка помилок в VB .NET
  • Форми Windows, графічний вивід і друк
  • Уведення-виведення
  •  Багатопотокові застосування
  • Підтримка баз-даних в VB .NET
  • Короткий огляд ASP .NET
  • Складки .NET, установка додатків і COM Interop
  • Книга для гостей
    Контакти
    Добавити у вибране

    :: Друзі ::

     
     

    :: Лічильники ::

    = =

     

     

     

     

    Взаємне блокування

    В процесі синхронізації блокування встановлюється для об'єктів, а не потоків, тому при використанні різних об'єктів для блокування різних фрагментів коди в програмах іноді виникають вельми нетривіальні помилки. На жаль, у багатьох випадках синхронізація по одному об'єкту просто недопустима, оскільки вона приведе до дуже частого блокування потоків.

    Розглянемо ситуацію взаємного блокування (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.

    1. Рядки 26 і 27 просто виводять на консоль налагоджувальну інформацію. У основному коді методу (рядки 28-36) доступ до вилки синхронізується по об'єктній змінній Me. Оскільки в нашій програмі використовується тільки одна вилка, синхронізація по Me гарантує, що два потоки не зможуть одночасно захопити її. Команда Slee'p (у блоці, що починається в рядку 34) імітує затримку між захопленням вилки/ножа і початком їжі. Врахуйте, що команда Sleep не знімає блокування з об'єктів і лише прискорює виникнення взаємного блокування!
      Проте найбільший інтерес представляє код класу Programmer (рядки 61-108). У рядках 67-70 визначається загальний конструктор, що гарантує наявність в програмі тільки одній вилки і ножа. Код властивостей (рядки 74-94) простий і не вимагає коментарів. Найголовніше відбувається в методі Eat, що виконується двома окремими потоками. Процес продовжується в циклі до тих пір, поки який-небудь потік не захопить вилку разом з ножем. У рядках 98-102 об'єкт випадковим чином захоплює вилку/ніж, використовуючи виклик Rnd, — саме це і породжує взаємне блокування. Відбувається наступне:
      Потік, що виконує метод Eat об'єкту Той, активізується і входить в цикл. Він захоплює ніж і переходить в стан очікування.
    2. Потік, що виконує метод Eat об'єкту Bob, активізується і входить в цикл. Він не може захопити ніж, але захоплює вилку і переходить в стан очікування.
    3. Потік, що виконує метод Eat об'єкту Той, активізується і входить в цикл. Він намагається захопити вилку, проте вилка вже захоплена об'єктом Bob; потік переходить в стан очікування.
    4. Потік, що виконує метод Eat об'єкту Bob, активізується і входить в цикл. Він намагається захопити ніж, проте ніж вже захоплений об'єктом Той; потік переходить в стан очікування.

    Все це продовжується до безкінечності — перед нами типова ситуація взаємного блокування (спробуйте запустити програму, і ви переконаєтеся в тому, що поїсти так нікому і не вдається).
    Про виникнення взаємного блокування можна дізнатися і у вікні потоків. Запустите програму і перервіть її клавішами Ctrl+break. Включите у вікно перегляду змінну Me і відкрийте вікно потоків. Результат виглядає приблизно так, як показано на мал. 10.7. З малюнка видно, що потік Bob захопив ніж, але вилки у нього немає. Клацніть правою кнопкою миші у вікні потоків на рядку Той і виберіть в контекстному меню команду Switch to Thread. Вікно перегляду показує, що у потоку Той є вилка, але немає ножа. Звичайно, це не є стовідсотковим доказом, але подібна поведінка принаймні примушує запідозрити недобре.
    Якщо варіант з синхронізацією по одному об'єкту (як в програмі з підвищенням -температури в будинку) неможливий, для запобігання взаємним блокуванням можна пронумерувати об'єкти синхронізації і завжди захоплювати їх в постійному порядку. Продовжимо аналогію з програмістами, що обідають: якщо потік завжди спочатку бере ніж, а потім вилку, проблем з взаємним блокуванням не буде. Перший потік, що захопив ніж, зможе нормально поїсти. У перекладі мовою програмних потоків це означає, що захоплення об'єкту 2 можливий лише за умови попереднього захоплення об'єкту 1.

    Мал. 10.7. Аналіз взаємного блокування у вікні потоків

    Отже, якщо прибрати виклик Rnd в рядку 98 і замінити його фрагментом

    mfork.GrabFork(Me)

    mknife.GrabKnife(Me)

    взаємне блокування зникає!

     




    :: Наша кнопка ::

    Отримати код:

    Підтримайте наш сайт і розмістіть нашу кнопку на своєму ресурсі.


    :: Реклама ::

    Скачати безкоштовно програму Microsoft Front Page 2003


    :: Посилання ::

    -


     

     

     


    Copyright ©