Під час написання багатопотокових програм однією з найпоширеніших проблем є тупикові місця.
Мої запитання до громади:
Що таке тупик?
Як ви їх виявляєте?
Ви справляєтесь з ними?
І нарешті, як ви запобігаєте їх виникненню?
Під час написання багатопотокових програм однією з найпоширеніших проблем є тупикові місця.
Мої запитання до громади:
Що таке тупик?
Як ви їх виявляєте?
Ви справляєтесь з ними?
І нарешті, як ви запобігаєте їх виникненню?
Відповіді:
Блокування відбувається , коли кілька процесів намагаються отримати доступ до одного ресурсу одночасно.
Один процес втрачається і потрібно чекати, коли інший закінчиться.
Тупиковий відбувається , коли процес очікування все ще тримається на інший ресурс , що перші потреби до нього можна закінчити.
Отже, приклад:
Ресурс A і ресурс B використовуються в процесі X і Y
Найкращий спосіб уникнути тупикових ситуацій - уникнути перехресних процесів таким чином. Зменшіть необхідність заблокувати що-небудь наскільки можна.
У базах даних уникайте внесення великої кількості змін у різні таблиці за одну транзакцію, уникайте тригерів та переходьте до читання оптимістичних / брудних / блокування якомога більше.
Дозвольте мені пояснити реальний (не насправді реальний) приклад ситуації з тупиком із кримінальних фільмів. Уявіть, що злочинець тримає в заручниках, і проти цього, поліцейський також тримає заручника, який є другом злочинця. У цьому випадку злочинець не збирається відпускати заручника, якщо поліцейський не відпустить свого друга. Також поліцейський не збирається відпускати друга злочинця, якщо злочинець не звільнить заручника. Це нескінченна недостовірна ситуація, адже обидві сторони наполягають на першому кроці один від одного.
Так просто, коли для двох потоків потрібні два різні ресурси, і кожен з них має блокування ресурсу, який потребує інший, це глухий кут.
Ви зустрічаєтесь з дівчиною, і один день після суперечки обидві сторони розбиті один від одного і чекають, коли ви зателефонуєте мені, пробачте, і я пропустив . У цій ситуації обидві сторони хочуть спілкуватися один з одним і тоді, і лише в тому випадку, якщо одна з них отримує дзвінок, на який я шкодую . Тому що жоден з них не збирається розпочинати спілкування і чекати в пасивному стані, обидва будуть чекати, коли інший розпочне спілкування, яке закінчиться в тупиковій ситуації.
Тупики відбудуться лише тоді, коли у вас є два або більше замків, які можна одночасно отримати, і вони схоплені в іншому порядку.
Способи уникнути тупикових ситуацій:
Щоб визначити тупик, спочатку я визначив би процес.
Процес : Як ми знаємо, процес є не що інше, як program
виконання.
Ресурс : для виконання програмного процесу потрібні деякі ресурси. Категорії ресурсів можуть включати пам'ять, принтери, процесори, відкриті файли, магнітофони, компакт-диски тощо.
Тупик : ситуація або стан, коли два або більше процесів містять деякі ресурси і намагаються придбати ще кілька ресурсів, і вони не можуть звільнити ресурси, поки не закінчать їх виконання.
Стан або ситуація з тупиком
На наведеній діаграмі є два процеси P1 і p2 і є два ресурси R1 і R2 .
Ресурс R1 виділяється для процесу P1, а ресурс R2 виділяється для процесу p2 . Для завершення виконання процесу P1 потрібен ресурс R2 , тому P1 запит на R2 , але R2 вже призначений P2 .
Таким же чином Процес P2 для завершення його виконання потребує R1 , але R1 вже призначений P1 .
обидва процеси не можуть звільнити свій ресурс до тих пір, поки вони не завершать їх виконання. Тож обидва чекають іншого ресурсу, і вони будуть чекати вічно. Отже, це умова ГРИШОГО .
Для того, щоб стався тупик, повинні бути справжніми чотири умови.
і всі ці умови виконуються на наведеній вище схемі.
Тупик буває, коли нитка чекає чогось, що ніколи не виникає.
Зазвичай це буває, коли нитка чекає на мютекс або семафор, які ніколи не випускався попереднім власником.
Також часто трапляється, коли у вас виникають ситуації, пов’язані з двома потоками та двома блоками на зразок цього:
Thread 1 Thread 2
Lock1->Lock(); Lock2->Lock();
WaitForLock2(); WaitForLock1(); <-- Oops!
Ви, як правило, виявляєте їх, оскільки речі, які, як ви очікуєте, відбудуться ніколи, або програма висить повністю.
Ви можете подивитися ці чудові статті у розділі Тупик . Він є в C #, але ідея все ще така ж і для інших платформ. Я цитую тут для легкого читання
Тупик буває, коли дві нитки чекають ресурсу, який тримає інший, тому жоден не може продовжувати. Найпростіший спосіб проілюструвати це двома замками:
object locker1 = new object();
object locker2 = new object();
new Thread (() => {
lock (locker1)
{
Thread.Sleep (1000);
lock (locker2); // Deadlock
}
}).Start();
lock (locker2)
{
Thread.Sleep (1000);
lock (locker1); // Deadlock
}
Тупик є поширеною проблемою при багатопроцесорних / багатопрограмових проблемах в ОС. Скажімо, є два процеси P1, P2 та два загальнодоступні ресурси R1, R2, а в критичному розділі обом ресурсам потрібно отримати доступ
Спочатку ОС призначає R1 для обробки P1 і R2 для обробки P2. Оскільки обидва процеси працюють одночасно, вони можуть почати виконувати свій код, але ПРОБЛЕМА виникає, коли процес потрапляє у критичний розділ. Тож процес R1 буде чекати, коли процес P2 випустить R2 і навпаки ... Отже, вони будуть чекати назавжди (УМОВЛЕННЯ РОЗВИТКУ).
Невелика АНАЛОГІЯ ...
Ваша мати (ОС),
ви (P1),
ваш брат (P2),
Apple (R1),
ніж (R2),
критичний розділ (різання яблука ножем).Твоя мати на початку дає тобі яблуко і ніж для брата.
Обоє радіють і грають (виконуючи свої коди).
Хтось із вас хоче в якийсь момент вирізати яблуко (критичний переріз).
Ви не хочете дарувати яблуко своєму братові.
Твій брат не хоче тобі давати ніж.
Тож ви обидва будете чекати дуже довго-довго :)
Тупик виникає, коли два потоки мають замки, які не дозволяють прогресувати жодному з них. Найкращий спосіб уникнути їх - ретельний розвиток. Багато вбудованих систем захищають від них, використовуючи сторожовий таймер (таймер, який скидає систему кожного разу, якщо вона зависає протягом певного періоду часу).
Тупик виникає, коли є круговий ланцюг ниток або процесів, кожен з яких містить заблокований ресурс і намагається зафіксувати ресурс, що знаходиться у чергового елемента ланцюга. Наприклад, дві нитки, що містять відповідно блокування A і блокування B, і обидва намагаються придбати інший замок.
Класична і дуже проста програма для розуміння ситуації з тупиком : -
public class Lazy {
private static boolean initialized = false;
static {
Thread t = new Thread(new Runnable() {
public void run() {
initialized = true;
}
});
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
System.out.println(initialized);
}
}
Коли головний потік викликає Lazy.main, він перевіряє, чи клас Lazy був ініціалізований і починає ініціалізувати клас. Тепер основний потік встановлює ініціалізований на false, створює і запускає фоновий потік, метод запуску якого встановлений ініціалізовано в true, і чекає завершення фонового потоку.
Цього разу клас ініціалізується іншим потоком. За цих обставин поточний потік, який є фоновим потоком, чекає на об’єкт Class до завершення ініціалізації. На жаль, нитка, яка робить ініціалізацію, основний потік, чекає завершення фонового потоку. Оскільки два потоки тепер чекають один одного, програма ЗАВЕРШЕНО.
Тупиковий стан - це стан системи, в якому жоден процес / потік не здатний виконати дію. Як зазначають інші, тупикова ситуація, як правило, є результатом ситуації, коли кожен процес / потік бажає придбати блокування до ресурсу, який вже заблокований іншим (або навіть тим самим) процесом / потоком.
Існують різні методи їх пошуку та уникнення. Кожен думає дуже важко і / або намагається багато речей. Однак боротися з паралелізмом, як відомо, важко, і більшість (якщо не всі) люди не зможуть повністю уникнути проблем.
Деякі формальніші методи можуть бути корисними, якщо ви серйозно ставитеся до таких питань. Найбільш практичний метод, про який я знаю, - це використовувати теоретичний підхід до процесу. Тут ви моделюєте свою систему деякою мовою процесу (наприклад, CCS, CSP, ACP, mCRL2, LOTOS) і використовуєте доступні інструменти для (моделювання) перевірки на наявність тупикових ситуацій (а можливо, і інших властивостей). Прикладами набору інструментів є FDR, mCRL2, CADP та Uppaal. Деякі сміливі душі можуть навіть довести свою систему без тупика, використовуючи суто символічні методи (доказ теореми; шукайте Овікі-Гріса).
Однак зазвичай ці формальні методи вимагають певних зусиль (наприклад, вивчення основ теорії процесів). Але я думаю, що це просто наслідок того, що ці проблеми важкі.
Тупик - це ситуація, коли наявна кількість ресурсів є меншою, оскільки цього вимагає різний процес. Це означає, що коли кількість доступних ресурсів стає меншою, ніж вимагає користувач, то в цей час процес переходить у стан очікування. Деякі час очікування збільшується більше, і немає жодного шансу перевірити проблему нестачі ресурсів, тоді ця ситуація відома як тупик. Насправді тупик є головною проблемою для нас, і він виникає лише в багатозадачній операційній системі .deadlock не може виникнути в операційній системі з одним завданням, оскільки всі ресурси присутні лише для того завдання, яке зараз виконується ......
Вище деякі пояснення приємні. Сподіваємось, це також може бути корисним: https://ora-data.blogspot.in/2017/04/deadlock-in-oracle.html
У базі даних, коли сеанс (наприклад, ora) хоче ресурс, який зберігається іншим сеансом (наприклад, дані), але цей сеанс (дані) також хоче ресурс, який проводиться першим сеансом (ora). Також може бути більше 2 сеансів, але ідея буде такою ж. Насправді, тупики заважають деяким транзакціям продовжувати працювати. Наприклад: Припустимо, ORA-DATA містить блокування A і запит блокування B, а SKU містить блокування B і запит блокування A.
Дякую,
Тупики виникають не просто із замками, хоча це найчастіша причина. У C ++ ви можете створити тупик з двома потоками і без блокування, просто встановивши кожен виклик потоку join () на об'єкт std :: thread для іншого.
Використання блокування для контролю доступу до спільних ресурсів схильне до тупикових ситуацій, і сам планувальник транзакцій не може запобігти їх виникненню.
Наприклад, системи реляційних баз даних використовують різні блокування для гарантії властивостей ACID транзакцій .
Незалежно від того, яку реляційну систему баз даних ви використовуєте, блокування завжди отримуватиметься під час зміни (наприклад, UPDATE
або DELETE
) певного запису таблиці. Без блокування рядка, зміненого поточною транзакцією, Atomicity буде порушена .
Як я пояснив у цій статті , тупикова ситуація трапляється, коли дві одночасні транзакції не можуть досягти прогресу, оскільки кожна чекає, коли інша звільнить замок, як показано на наступній схемі.
Оскільки обидві транзакції знаходяться у фазі придбання блокування, жодна з них не звільняє блокування до отримання наступної.
Якщо ви використовуєте алгоритм контролю сумісності, який спирається на блокування, то завжди існує ризик запуску в тупиковій ситуації. Тупики можуть виникати в будь-якому середовищі одночасності, а не лише в системі баз даних.
Наприклад, багатопотокова програма може зайти в глухий кут, якщо два або більше потоків чекають на попередньо придбані замки, так що жоден потік не може досягти прогресу. Якщо це відбувається в додатку Java, JVM не може просто змусити Thread зупинити його виконання та звільнити його замки.
Навіть якщо Thread
клас виставляє stop
метод, цей метод був застарілий з Java 1.1, оскільки він може спричинити залишення об'єктів у непослідовному стані після зупинки потоку. Натомість Java визначає interrupt
метод, який діє як натяк, оскільки нитка, яка переривається, може просто ігнорувати переривання і продовжувати його виконання.
З цієї причини програма Java не може відновитись із ситуації, що склалася, і відповідальність розробника додатка є замовляти запити на придбання блокування таким чином, що ніколи не може виникнути тупикова ситуація.
Однак система баз даних не може примусити заданий замовлення на придбання блокування, оскільки неможливо передбачити, які інші блокування певна транзакція захоче придбати далі. За збереження порядку блокування стає відповідальністю шару доступу до даних, і база даних може лише допомогти відновитись із тупикової ситуації.
Двигун бази даних виконує окремий процес, який сканує поточний графік конфлікту для циклів блокування-очікування (які викликані тупиковими блоками). Коли цикл виявляється, двигун бази даних вибирає одну транзакцію і перериває її, викликаючи звільнення її блокувань, щоб інша транзакція могла прогресувати.
На відміну від JVM, транзакція з базою даних розроблена як атомна одиниця роботи. Отже, відкат залишає базу даних у послідовному стані.
Більш детально про цю тему ознайомтеся і з цією статтею .
Mutex по суті - це замок, що забезпечує захищений доступ до спільних ресурсів. Під Linux типом даних mutex для потоку є pthread_mutex_t. Перед використанням ініціалізуйте його.
Щоб отримати доступ до спільних ресурсів, вам потрібно заблокувати мутекс. Якщо мютекс уже заблокований, виклик заблокує потік, поки не буде розблокована мютекс. Після завершення відвідування спільних ресурсів ви повинні їх розблокувати.
Загалом, існує кілька неписаних основних принципів:
Отримайте блокування перед використанням спільних ресурсів.
Тримати замок якомога коротше.
Відпустіть замок, якщо потік поверне помилку.