Відповіді:
Взято з http://en.wikipedia.org/wiki/Deadlock :
У паралельних обчисленнях тупиковий стан - це стан, при якому кожен член групи дій чекає, коли інший член випустить блокування
Рівень живлення схожий з тупиком, за винятком того, що стани процесів, що беруть участь у лайволкені, постійно змінюються один щодо одного, не прогресуючи. Livelock - особливий випадок голодного ресурсу; загальне визначення лише говорить про те, що конкретний процес не прогресує.
Приклад реальної життєдіяльності виникає, коли двоє людей зустрічаються у вузькому коридорі, і кожен намагається бути ввічливим, рухаючись убік, щоб пропустити іншого, але вони в кінцевому підсумку колишуться з боку в бік, не роблячи жодного прогресу, оскільки вони обоє неодноразово рухаються однаково одночасно.
Livelock - це ризик за допомогою деяких алгоритмів, які виявляють та відновлюються з тупикової ситуації. Якщо більше одного процесу вживає заходів, алгоритм виявлення глухого кута може повторно запускатися. Цього можна уникнути, гарантуючи, що лише один процес (вибраний випадковим чином або за пріоритетом) вживає заходів.
Нитка часто діє у відповідь на дію іншої нитки. Якщо дія іншої нитки також є відповіддю на дію іншої нитки, то може виникнути лайволк.
Як і у глухому куті, закручені нитки не в змозі досягти подальшого прогресу . Однак потоки не заблоковані - вони просто занадто зайняті реагуванням один на одного, щоб відновити роботу . Це можна порівняти з двома людьми, які намагаються пройти один одного в коридорі: Альфонс рухається ліворуч, щоб пропустити Гастона, а Гастон рухається праворуч, щоб Альфонс пройшов. Побачивши, що вони все ще блокують один одного, Альфонс рухається праворуч, а Гастон - ліворуч. Вони все ще блокують одне одного, і так далі ...
Основна відмінність між активної блокуванням і тупиком , що потоки не будуть заблоковані, замість цього вони будуть намагатися реагувати один з одним безперервно.
У цьому зображенні обидва кола (нитки чи процеси) намагатимуться дати простір іншому, рухаючись ліворуч та праворуч. Але вони не можуть рухатися далі.
Весь зміст та приклади тут
Операційні системи: внутрішні принципи та принципи дизайну
William Stallings
8º Edition
Тупик : ситуація, коли два або більше процесів не в змозі протікати, тому що кожен чекає, коли інші щось робити.
Наприклад, розглянемо два процеси, P1 і P2, і два ресурси, R1 і R2. Припустимо, що кожен процес потребує доступу до обох ресурсів, щоб виконувати частину своєї функції. Тоді можлива така ситуація: ОС призначає R1 P2, а R2 P1. Кожен процес чекає одного з двох ресурсів. Жоден ресурс не звільнятиме ресурс, яким він уже володіє, поки не придбає інший ресурс і не виконає функцію, що вимагає обох ресурсів. Два процеси в тупику
Livelock : ситуація, коли два або більше процесів постійно змінюють свої стани у відповідь на зміни в інших процесах, не виконуючи жодної корисної роботи:
Голодування : ситуація, коли планувальник нескінченно ігнорує розпорядницький процес; хоча він може продовжувати, він ніколи не обирається.
Припустимо, що три процеси (P1, P2, P3) потребують періодичного доступу до ресурсу Р. Розгляньте ситуацію, в якій P1 володіє ресурсом, і обидва P2 і P3 затримуються, чекаючи цього ресурсу. Коли P1 виходить з критичного розділу, P2 або P3 повинен бути дозволений доступ до R. Припустимо, що ОС надає доступ до P3 і що P1 знову вимагає доступу до того, як P3 завершить свій критичний розділ. Якщо ОС надає доступ до P1 після закінчення P3, а згодом по черзі надає доступ до P1 і P3, то P2 може бути безстроково відмовлено у доступі до ресурсу, навіть якщо ситуація з тупиком не існує.
ДОДАТОК А - ТЕМИ В СЛУЧАЙНІСТЬ
Приклад тупикового зв'язку
Якщо обидва процеси встановлять свої прапори до істини, перш ніж будь-який виконав оператор while, кожен з них подумає, що інший увійшов у свій критичний розділ, викликаючи тупик.
/* PROCESS 0 */
flag[0] = true; // <- get lock 0
while (flag[1]) // <- is lock 1 free?
/* do nothing */; // <- no? so I wait 1 second, for example
// and test again.
// on more sophisticated setups we can ask
// to be woken when lock 1 is freed
/* critical section*/; // <- do what we need (this will never happen)
flag[0] = false; // <- releasing our lock
/* PROCESS 1 */
flag[1] = true;
while (flag[0])
/* do nothing */;
/* critical section*/;
flag[1] = false;
Приклад Livelock
/* PROCESS 0 */
flag[0] = true; // <- get lock 0
while (flag[1]){
flag[0] = false; // <- instead of sleeping, we do useless work
// needed by the lock mechanism
/*delay */; // <- wait for a second
flag[0] = true; // <- and restart useless work again.
}
/*critical section*/; // <- do what we need (this will never happen)
flag[0] = false;
/* PROCESS 1 */
flag[1] = true;
while (flag[0]) {
flag[1] = false;
/*delay */;
flag[1] = true;
}
/* critical section*/;
flag[1] = false;
[...] розглянемо таку послідовність подій:
Цю послідовність можна було продовжити на невизначений термін, і жоден процес не міг увійти в її критичний розділ. Суворо кажучи, це не тупик , оскільки будь-яка зміна відносної швидкості двох процесів порушить цей цикл і дозволить вступити в критичний розділ. Цей стан називають живітним . Нагадаємо, що тупикова ситуація виникає тоді, коли набір процесів бажає ввести свої критичні розділи, але жоден процес не може досягти успіху. За допомогою лайволака можливі послідовності виконання, які досягають успіху, але також можна описати одну або кілька послідовностей виконання, в яких жоден процес ніколи не вступає в його критичний розділ.
Більше не вміст із книги.
А як щодо спинлок?
Spinlock - це методика уникнення витрат механізму блокування ОС. Зазвичай ви робите:
try
{
lock = beginLock();
doSomething();
}
finally
{
endLock();
}
Проблема починає з’являтися, коли beginLock()
коштує набагато більше doSomething()
. У дуже перебільшеному вигляді уявіть, що відбувається, коли beginLock
витрачається 1 секунда, але doSomething
коштує всього 1 мілісекунда.
У цьому випадку, якщо ви зачекали 1 мілісекунду, вам не вдасться перешкодити 1 секунду.
Чому beginLock
б коштував стільки? Якщо замок вільний - це не багато коштує (див. Https://stackoverflow.com/a/49712993/5397116 ), але якщо замок не вільний, ОС "заморозить" вашу нитку, встановіть механізм, щоб вас розбудити коли замок буде звільнено, а потім знову розбудить вас.
Все це набагато дорожче, ніж деякі петлі, що перевіряють замок. Ось чому іноді краще робити «спінлок».
Наприклад:
void beginSpinLock(lock)
{
if(lock) loopFor(1 milliseconds);
else
{
lock = true;
return;
}
if(lock) loopFor(2 milliseconds);
else
{
lock = true;
return;
}
// important is that the part above never
// cause the thread to sleep.
// It is "burning" the time slice of this thread.
// Hopefully for good.
// some implementations fallback to OS lock mechanism
// after a few tries
if(lock) return beginLock(lock);
else
{
lock = true;
return;
}
}
Якщо ваша реалізація не є обережною, ви можете потрапити на мобільний телефон, витративши весь процесор на механізм блокування.
Також дивіться:
https://preshing.com/20120226/roll-your-own-lightweight-mutex/
Чи правильна та оптимальна реалізація мого відключення спина?
Підсумок :
Тупик : ситуація, коли ніхто не просувається, нічого не робить (спить, чекає тощо). Використання процесора буде низьким;
Livelock : ситуація, коли ніхто не прогресує, але процесор витрачається на механізм блокування, а не на ваш розрахунок;
Голодування: ситуація, коли один прокурор ніколи не отримує шансу балотуватися; чистою невдачею чи якоюсь його властивістю (наприклад, низький пріоритет);
Спінлок : техніка уникнення вартості очікування звільнення замка.
DEADLOCK Тупик - це умова, при якій завдання нескінченно чекає умов, які ніколи не можуть бути виконані - завдання вимагає виключного контролю над спільними ресурсами - завдання тримає ресурси під час очікування звільнення інших ресурсів - завдання не можуть бути змушені скорочувати ресурси - кругові очікування умова існує
Умови LIVELOCK Livelock можуть виникати, коли від двох або більше завдань залежать і використовують деякий ресурс, спричиняючи умову кругової залежності, коли ці завдання продовжуються виконуватись назавжди, тим самим блокуючи виконання всіх завдань нижчого пріоритетного рівня (ці завдання з нижчим пріоритетом відчувають стан, який називають голодуванням)
Можливо, ці два приклади ілюструють вам різницю між тупиком та рівнем життя:
Java-приклад для глухого кута:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DeadlockSample {
private static final Lock lock1 = new ReentrantLock(true);
private static final Lock lock2 = new ReentrantLock(true);
public static void main(String[] args) {
Thread threadA = new Thread(DeadlockSample::doA,"Thread A");
Thread threadB = new Thread(DeadlockSample::doB,"Thread B");
threadA.start();
threadB.start();
}
public static void doA() {
System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
lock1.lock();
System.out.println(Thread.currentThread().getName() + " : holds lock 1");
try {
System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
lock2.lock();
System.out.println(Thread.currentThread().getName() + " : holds lock 2");
try {
System.out.println(Thread.currentThread().getName() + " : critical section of doA()");
} finally {
lock2.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
}
} finally {
lock1.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
}
}
public static void doB() {
System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
lock2.lock();
System.out.println(Thread.currentThread().getName() + " : holds lock 2");
try {
System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
lock1.lock();
System.out.println(Thread.currentThread().getName() + " : holds lock 1");
try {
System.out.println(Thread.currentThread().getName() + " : critical section of doB()");
} finally {
lock1.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
}
} finally {
lock2.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
}
}
}
Вибірка зразка:
Thread A : waits for lock 1
Thread B : waits for lock 2
Thread A : holds lock 1
Thread B : holds lock 2
Thread B : waits for lock 1
Thread A : waits for lock 2
Java-приклад для lvelock:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LivelockSample {
private static final Lock lock1 = new ReentrantLock(true);
private static final Lock lock2 = new ReentrantLock(true);
public static void main(String[] args) {
Thread threadA = new Thread(LivelockSample::doA, "Thread A");
Thread threadB = new Thread(LivelockSample::doB, "Thread B");
threadA.start();
threadB.start();
}
public static void doA() {
try {
while (!lock1.tryLock()) {
System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
Thread.sleep(100);
}
System.out.println(Thread.currentThread().getName() + " : holds lock 1");
try {
while (!lock2.tryLock()) {
System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
Thread.sleep(100);
}
System.out.println(Thread.currentThread().getName() + " : holds lock 2");
try {
System.out.println(Thread.currentThread().getName() + " : critical section of doA()");
} finally {
lock2.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
}
} finally {
lock1.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
}
} catch (InterruptedException e) {
// can be ignored here for this sample
}
}
public static void doB() {
try {
while (!lock2.tryLock()) {
System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
Thread.sleep(100);
}
System.out.println(Thread.currentThread().getName() + " : holds lock 2");
try {
while (!lock1.tryLock()) {
System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
Thread.sleep(100);
}
System.out.println(Thread.currentThread().getName() + " : holds lock 1");
try {
System.out.println(Thread.currentThread().getName() + " : critical section of doB()");
} finally {
lock1.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
}
} finally {
lock2.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
}
} catch (InterruptedException e) {
// can be ignored here for this sample
}
}
}
Вибірка зразка:
Thread B : holds lock 2
Thread A : holds lock 1
Thread A : waits for lock 2
Thread B : waits for lock 1
Thread B : waits for lock 1
Thread A : waits for lock 2
Thread A : waits for lock 2
Thread B : waits for lock 1
Thread B : waits for lock 1
Thread A : waits for lock 2
Thread A : waits for lock 2
Thread B : waits for lock 1
...
Обидва приклади змушують нитки отримувати замки в різних порядках. Поки тупик чекає іншого замка, лівеллок насправді не чекає - він відчайдушно намагається придбати замок, не маючи шансів його дістати. Кожна спроба споживає цикли процесора.
Уявіть, що ви маєте нитку A і потік B. Вони обидва synchronised
на одному об'єкті, і всередині цього блоку є глобальна змінна, яку вони обидва оновлюють;
static boolean commonVar = false;
Object lock = new Object;
...
void threadAMethod(){
...
while(commonVar == false){
synchornized(lock){
...
commonVar = true
}
}
}
void threadBMethod(){
...
while(commonVar == true){
synchornized(lock){
...
commonVar = false
}
}
}
Таким чином, коли потік А входить в while
петлю і утримує блокування, він робить те , що він повинен робити , і встановити commonVar
в true
. Потім входить нитка B, входить у while
цикл і, оскільки commonVar
є true
зараз, вона може утримувати замок. Це робиться так, виконує synchronised
блок і commonVar
повертається до false
. Тепер потік А знову отримує це нове вікно CPU, він був збирається вийти з while
циклу , але потік B тільки встановити його назад false
, так що цикл повторюється знову. Нитки роблять щось (тому вони не заблоковані в традиційному розумінні), але майже нічого.
Можливо, також приємно згадати, що тут не обов'язково з'являтись лайлекс. Я припускаю, що планувальник надає перевагу іншому потоку, як тільки synchronised
блок завершить виконання. Більшу частину часу я думаю, що це важко очікувати очікування і залежить від багатьох речей, що відбуваються під капотом.