Як уникнути взаімоблокіровок в Visual C # (Sharp)

При взаімоблокіровке (Deadlock) виконання коду припиняється Взаімоблоковка відбувається, коли один потік утримує блокування і очікує Інформ від іншого потоку Але інший потік не може надати цю інформацію першим, т к він очікує отримати блокування

Ще одна причина бути якомога розважливим з використанням блокувань, т к їх використання може викликати взаємоблокування Взаимоблокировки доставлт програмістам неабиякі неприємності Розглянемо, наприклад, наступний двухпоточний код для обробки колекції:

List&ltint&gt elements = new List&ltint&gt() elementsAdd(10)

elementsAdd(20)

Thread threadl = new Thread(

О => {

ThreadSleep(1000) int[] items

lock (elements) {

while(elementsCount &lt 3) { ThreadSleep(lOOO)

}

items = elementsToArray()

}

foreach (int item in items) { ConsoleWriteLine(&quotItem (&quot + item + &quot)&quot) ThreadSleep(1000)

}

})

Thread thread2 = new Thread(

О => {

ThreadSleep(1500)

lock (elements) { elementsAdd(30)

}

})

threadlStart() thread2Start()

Код циклу очікує, поки рахунок колекції не досягне 3 Але цього ніколи не злитися, т к потік, відповідальний за збільшення рахунку, очікує звільнення бліровкі Код, що виконує очікування, виділений жирним шрифтом У ньому випояется перевірка на досягнення рахунки колекції значення 3 Якщо приводу не дорівнює 3, то потік очікує 1 секунду і виконує перевірку знову Але під час очікування код не звільняє блокування, тому другий потік, що збільшує значення рахунку, перебуває в очікуванні Таким чином, у нас в наявності взаімоблокіровка

Не вдаючись до зміни блокувань, взаимоблокировки можна уникнути, злегка модифікувавши код таким чином:

List&ltint&gt elements = new List&ltint&gt() elements-Add(10)

elementsAdd(20)

Thread threadl = new Thread(

О => {

ThreadSleep(lOOO) int[] items

lock (elements) {

while (elementsCount &lt 3) { ThreadSleep(lOOO)

}

items = elementsToArray()

}  

foreach (int item in items) { ConsoleWriteLine(&quotItem (&quot + item + &quot)&quot) ThreadSleep(1000)

}

})

Thread thread2 = new Thread(

О => {

ThreadSleep(500)

lock (elements) {

}

})

elementsAdd(30)

threadlStart() thread2Start()

Код стає працездатним завдяки одному зміни (виділено жіим шрифтом) У первинному варіанті читає потік виконувався перший, а в другому варіанті першого виповнюється записуючий потік Це демонстрірт, що взаимоблокировки часто викликаються неправильними тимчасовими соотненіямі

Що робить виявлення причин взаімоблокіровок особливо важким, так це їх недетермінірованного поведінку При детермінованому поведінці одне і те ж дія завжди дає один і той же результат Більшість вихідного коду раба детерміноване Тому зазвичай в пошуках причини помилки у звичайному ке ми не розглядаємо поведінку коду на багато кроків вперед, а просто сістеміческі опрацьовуємо код, поки не виявимо її Але поведінка потокового коду не є детермінованим, т к його поведінка може бути змінено времеимі співвідношеннями Вплив на тимчасові співвідношення можуть надавати раічние фактори: поділ ресурсів, швидкість мікропроцесора, застосування відладчика і безліч інших

Щоб зробити код детермінованим, необхідно виправити фрагмент коду, який утримує блокування в той час, коли він не повинен цього робити Памятайте основне правило для застосування блокувань: код повинен утримувати блокування якомога короткий час

Нам необхідно використовувати більш витончений конструктив блокування, позвяющій очікувати, поки дані не стануть доступними У NET є досить багато конструктивів для роботи з потоками і їх синхронізації, кожен з котих специфічний для певного типу проблеми У разі взаімоблокіровок нам потрібно використовувати тип Monitor Це синхронізаційних тип високого клаа, що дозволяє інформувати очікують потоки про стан блокування

Для пояснення цього типу повернемося до нашої аналогії з кількома кухарями в одній кухні Скажімо, що одному з них потрібен особливий ніж для нарізки філе риби, але цей ніж використовується іншим кухарем Що робить наш кухар в такому випадку Стоїть біля іншого кухаря, нетерпляче постукуючи ногою, або ж просить його дати йому знати, коли ніж звільниться, а тим часом іде займатися чимось іншим, благо в кухні роботи достатньо Швидше за все, друге

Дана потужна концепція спільної роботи і сповіщення інших користувачів запрограмована в тип Monitor Він може захопити блокування, віддати іншим користувачам і взяти її назад

15 Зак 555

При роботі з типом Monitor ми не оголошуємо захищений блок коду, тому що цей тип дозволяє набагато більше гнучкості Наприклад, можна визначити клас з мехізмом блокування на рівні примірника, як показано в наступному коді:

class DoSomething { public void GetLock() {

MonitorEnter(elements)

}

public void ReleaseLock() { MonitorExit(this)

}

}

Обєкт Monitor можна розмістити в будь-якому місці коду, але він привязується до оеделенному потоку Тому, коли потік захоплює обєкт Monitor, він удеівает управління до тих пір, поки не завершить виконання або не звільнить обкт Це також приносить додаткову користь у тому, що, отримавши блокування, Monitor може продовжувати отримувати її знову і знову Але якщо потік захопить Monitor, скажімо, пять разів, він також повинен звільнити його пять разів, перш ніж інший потік може захопити блокування

Далі наводиться код обробки колекції в двох потоках, модифікований під використання типу Monitor:

List&ltint&gt elements = new List&ltint&gt() elementsAdd(10)

elementsAdd(20)

Thread threadl = new Thread(

О => {

ThreadSleep(1000) int[] items MonitorEnter(elements)

while (elementsCount &lt 3) {

MonitorWait(elements, 1000)

}

items = elementsToArray() MonitorExit(elements) foreach (int item in items) {

ConsoleWriteLine(&quotItem (&quot + item + &quot)&quot) ThreadSleep(1000)

}

})

Thread thread2 = new Thread(

( ) = &gt {

ThreadSleep(1500) MonitorEnter(elements) elementsAdd(30) MonitorPulse(elements) MonitorExit(elements)

})

threadlStart() thread2Start()

Жирним шрифтом виділені модифікації, що використовують тип Monitor У опреденіе потоку, який отримує блокування першим, метод Monitor Enter про вивается з параметром elements, який, як і в попередньому прикладі блокування, визначає область блокування Отримавши блокування, потік перевіряє, чи дорівнює значення рахунку колекції 3 або більше Якщо значення лічильника менше 3, то викликається метод Monitor Wait о Метод Monitor Wait () працює подібно мету Thread Sleep (), ТІЛЬКИ блокування Monitor звільняється

Звільнення блокування є особливою можливістю типу Monitor Але бліровка звільняється тільки на час, коли управління має метод Monitor wait () По поверненню управління методом Monitor Wait () код знову отримує блокування Коли через 1 секунду потік виходить з режиму сну, він більше не має блокування і повинен очікувати, щоб отримати її Якщо інший потік утримує блокування тривалий час, перший потік може очікувати тривалий час, щоб отримати її знову

Іншим способом завершення методу Monitor wait () Є отримання спецльного сигналу ВІД Інші потоки, В ЯКОМУ крім методу Enter про і Exit про також використовується метод Pulse о Метод Monitor Pulse про активізує сигнал, який пробуджує перший потік, але виконуватися цей потік буде тількипісля того, як другий потік звільнить блокування

Великою перевагою типу Monitor в порівнянні з оператором lock є те, що Monitor можна використовувати в будь-якому місці в коді, а також те, що він освождает блокування на час очікування відповіді Оператор lock застосовується в тому випадку, коли необхідно контролювати доступ до блоку коду Якщо ж доступ не обмежений межами методу, тоді краще використовувати тип Monitor Це не означає, що оператор lock не можна застосовувати поза блоком коду, але якщо нуо додати код, який може викликати взаємоблокування, то управляти кодом легше З ДОПОМОГОЮ типу Monitor

Тепер, коли у нас є базові знання про багатопоточності, в наступних Раель ми розглянемо більш складні потокові архітектури Особливу увагу буде приділено трьом методиками програмування: читач / письменник (Reader / writer), постачальник / споживач (producer / consumer) і асинхронні виклики

Джерело: Гросс К С # 2008: Пер з англ – СПб: БХВ-Петербург, 2009 – 576 е: ил – (Самовчитель)

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*