Chciałbym wyjaśnić początkującym wątki zakleszczeń. Widziałem w przeszłości wiele przykładów zakleszczeń, niektóre wykorzystujące kod, a niektóre wykorzystujące ilustracje (jak słynne 4 samochody ). Istnieją również klasyczne problemy , które można łatwo zablokować, takie jak The Dining Philosophers , ale mogą one być zbyt złożone, aby prawdziwy nowicjusz mógł je w pełni zrozumieć.
Szukam najprostszego przykładu kodu, aby zilustrować, czym są zakleszczenia. Przykład powinien:
- Odnoszą się do „prawdziwego” scenariusza programowania, który ma jakiś sens
- Pisz bardzo krótko, prosto i prosto do przodu
Co polecasz?
multithreading
language-agnostic
deadlock
Roee Adler
źródło
źródło
Odpowiedzi:
Może prosta sytuacja w banku.
class Account { double balance; void withdraw(double amount){ balance -= amount; } void deposit(double amount){ balance += amount; } void transfer(Account from, Account to, double amount){ sync(from); sync(to); from.withdraw(amount); to.deposit(amount); release(to); release(from); } }
Oczywiście, gdyby istniały dwa wątki, które będą próbowały uruchomić transfer ( a, b ) i transfer ( b, a ) w tym samym czasie, nastąpi impas, ponieważ będą próbowały pozyskać zasoby w odwrotnej kolejności.
Ten kod jest również świetny do szukania rozwiązań dla zakleszczenia. Mam nadzieję że to pomoże!
źródło
sync
może być coś takiego:sync(Account & a) { a.mutex.lock(); }
.Niech natura wyjaśni impas,
Deadlock: Frog vs. Snake
źródło
Oto przykład kodu z wydziału informatyki uniwersytetu na Tajwanie, przedstawiający prosty przykład w języku Java z blokowaniem zasobów. To jest dla mnie bardzo istotne w „prawdziwym życiu”. Kod poniżej:
/** * Adapted from The Java Tutorial * Second Edition by Campione, M. and * Walrath, K.Addison-Wesley 1998 */ /** * This is a demonstration of how NOT to write multi-threaded programs. * It is a program that purposely causes deadlock between two threads that * are both trying to acquire locks for the same two resources. * To avoid this sort of deadlock when locking multiple resources, all threads * should always acquire their locks in the same order. **/ public class Deadlock { public static void main(String[] args){ //These are the two resource objects //we'll try to get locks for final Object resource1 = "resource1"; final Object resource2 = "resource2"; //Here's the first thread. //It tries to lock resource1 then resource2 Thread t1 = new Thread() { public void run() { //Lock resource 1 synchronized(resource1){ System.out.println("Thread 1: locked resource 1"); //Pause for a bit, simulating some file I/O or //something. Basically, we just want to give the //other thread a chance to run. Threads and deadlock //are asynchronous things, but we're trying to force //deadlock to happen here... try{ Thread.sleep(50); } catch (InterruptedException e) {} //Now wait 'till we can get a lock on resource 2 synchronized(resource2){ System.out.println("Thread 1: locked resource 2"); } } } }; //Here's the second thread. //It tries to lock resource2 then resource1 Thread t2 = new Thread(){ public void run(){ //This thread locks resource 2 right away synchronized(resource2){ System.out.println("Thread 2: locked resource 2"); //Then it pauses, for the same reason as the first //thread does try{ Thread.sleep(50); } catch (InterruptedException e){} //Then it tries to lock resource1. //But wait! Thread 1 locked resource1, and //won't release it till it gets a lock on resource2. //This thread holds the lock on resource2, and won't //release it till it gets resource1. //We're at an impasse. Neither thread can run, //and the program freezes up. synchronized(resource1){ System.out.println("Thread 2: locked resource 1"); } } } }; //Start the two threads. //If all goes as planned, deadlock will occur, //and the program will never exit. t1.start(); t2.start(); } }
źródło
Jeśli method1 () i method2 () zostaną wywołane przez dwa lub wiele wątków, istnieje duża szansa na zakleszczenie, ponieważ jeśli wątek 1 uzyska blokadę na obiekcie String podczas wykonywania metody method1 (), a wątek 2 uzyska blokadę na obiekcie Integer podczas wykonywania metody Method2 () oba będą czekać na siebie, aby zwolnić blokadę na Integer i String, aby przejść dalej, co nigdy się nie stanie.
public void method1() { synchronized (String.class) { System.out.println("Acquired lock on String.class object"); synchronized (Integer.class) { System.out.println("Acquired lock on Integer.class object"); } } } public void method2() { synchronized (Integer.class) { System.out.println("Acquired lock on Integer.class object"); synchronized (String.class) { System.out.println("Acquired lock on String.class object"); } } }
źródło
Jeden z prostych przykładów impasu, z jakim się spotkałem.
public class SimpleDeadLock { public static Object l1 = new Object(); public static Object l2 = new Object(); private int index; public static void main(String[] a) { Thread t1 = new Thread1(); Thread t2 = new Thread2(); t1.start(); t2.start(); } private static class Thread1 extends Thread { public void run() { synchronized (l1) { System.out.println("Thread 1: Holding lock 1..."); try { Thread.sleep(10); } catch (InterruptedException e) {} System.out.println("Thread 1: Waiting for lock 2..."); synchronized (l2) { System.out.println("Thread 2: Holding lock 1 & 2..."); } } } } private static class Thread2 extends Thread { public void run() { synchronized (l2) { System.out.println("Thread 2: Holding lock 2..."); try { Thread.sleep(10); } catch (InterruptedException e) {} System.out.println("Thread 2: Waiting for lock 1..."); synchronized (l1) { System.out.println("Thread 2: Holding lock 2 & 1..."); } } } } }
źródło
private int index
to tam robi?Oto prosty przykład w C ++ 11.
#include <mutex> // mutex #include <iostream> // cout #include <cstdio> // getchar #include <thread> // this_thread, yield #include <future> // async #include <chrono> // seconds using namespace std; mutex _m1; mutex _m2; // Deadlock will occur because func12 and func21 acquires the two locks in reverse order void func12() { unique_lock<mutex> l1(_m1); this_thread::yield(); // hint to reschedule this_thread::sleep_for( chrono::seconds(1) ); unique_lock<mutex> l2(_m2 ); } void func21() { unique_lock<mutex> l2(_m2); this_thread::yield(); // hint to reschedule this_thread::sleep_for( chrono::seconds(1) ); unique_lock<mutex> l1(_m1); } int main( int argc, char* argv[] ) { async(func12); func21(); cout << "All done!"; // this won't be executed because of deadlock getchar(); }
źródło
Zobacz moją odpowiedź na to pytanie . Podsumowując, ilekroć dwa wątki muszą zdobyć dwa różne zasoby i robią to w różnych kolejności, możesz uzyskać zakleszczenie.
źródło
Jednym z przykładów, które przychodzą mi do głowy, jest scenariusz Stół, latarka i baterie. Wyobraź sobie latarkę i parę baterii umieszczoną na stole. Jeśli podejdziesz do tego stołu i złapiesz baterie, podczas gdy inna osoba będzie miała latarkę, oboje będziecie zmuszeni do niezręcznego wpatrywania się w siebie, czekając, kto pierwszy położy przedmiot z powrotem na stole. To jest przykład impasu. Ty i ta osoba czekacie na zasoby, ale nikt z was nie rezygnuje z ich zasobów.
Podobnie w programie, zakleszczenie występuje, gdy dwa lub więcej wątków (Ty i druga osoba) czekają na zwolnienie dwóch lub więcej blokad (latarki i baterii), a okoliczności w programie są takie, że blokady nigdy nie są zwalniane ( oboje macie jeden element układanki).
Jeśli znasz Javę, możesz przedstawić ten problem w następujący sposób:
Jeśli uruchomisz ten przykład, zauważysz, że czasami rzeczy działają dobrze i poprawnie. Ale czasami twój program po prostu niczego nie drukuje. Dzieje się tak, ponieważ jedna osoba ma baterie, a druga ma latarkę, która zapobiega włączeniu latarki, powodując impas.
Ten przykład jest podobny do przykładu podanego w samouczkach Java: http://docs.oracle.com/javase/tutorial/essential/concurrency/deadlock.html
Innym przykładem jest przykład pętli:
Ten przykład może albo drukować w kółko „Nieskończone”, albo w ogóle nie może drukować „Nieukończone”. Pierwsza ma miejsce, ponieważ pierwszy wątek uzyskuje blokadę klasy i nigdy jej nie zwalnia, uniemożliwiając dostęp do „stopLoop” przez drugi wątek. A ostatnia ma miejsce, ponieważ drugi wątek rozpoczął się przed pierwszym wątkiem, powodując, że zmienna „done” ma wartość true przed wykonaniem pierwszego wątku.
źródło
public class DeadLock { public static void main(String[] args) throws InterruptedException { Thread mainThread = Thread.currentThread(); Thread thread1 = new Thread(new Runnable() { @Override public void run() { try { mainThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread1.start(); thread1.join(); } }
źródło
Uważam jednak, że problem filozofów kulinarnych jest jednym z prostszych przykładów pokazania impasu, ponieważ 4 wymagania dotyczące impasu można łatwo zilustrować na rysunku (zwłaszcza cykliczne oczekiwanie).
Uważam, że przykłady ze świata rzeczywistego są znacznie bardziej zagmatwane dla początkującego, chociaż nie mogę teraz wymyślić dobrego scenariusza ze świata rzeczywistego na szczycie mojej głowy (jestem stosunkowo niedoświadczony w zakresie współbieżności w świecie rzeczywistym).
źródło
Niedawno zdałem sobie sprawę, że kłótnie między parami to nic innego jak impas ... gdzie zwykle jeden z procesów musi się zawiesić, aby go rozwiązać, oczywiście jest to mniej priorytetowy (Boy;)).
Oto analogia ...
Proces1: Dziewczyna (G) Proces2: Chłopiec (B)
Zasób 1: Przepraszam Zasób2 : Akceptowanie własnego błędu
Niezbędne warunki:
1. Wzajemne wykluczenie: Tylko jeden z G lub B może powiedzieć przepraszam lub zaakceptować swój błąd w danej chwili.
2. Wstrzymaj i czekaj: W jednej chwili jeden trzyma Przepraszam, a drugi Akceptując swój błąd, jeden czeka, aż Zaakceptowanie własnego błędu uwolni przepraszam, a drugi czeka, aż przepraszam, aby się uwolnić, akceptując swój błąd.
3. Brak uprzedzeń: nawet Bóg nie może zmusić B lub G do uwolnienia Przepraszam lub Akceptując własny błąd. I dobrowolnie? Czy ty żartujesz??
4. Oczekiwanie cykliczne: Ponownie, ten, kto trzyma przepraszam, czeka, aż inny zaakceptuje własne błędy, a jeden z nich akceptuje własne błędy, aby inny najpierw przeprosił. Więc to jest okrągłe.
Tak więc impas ma miejsce, gdy wszystkie te warunki obowiązują w tym samym czasie, i tak jest zawsze w walce par;)
Źródło: http://www.quora.com/Saurabh-Pandey-3/Posts/Never-ending-couple-fights-a-deadlock
źródło
Jeszcze jeden prosty przykład zakleszczenia z dwoma różnymi zasobami i dwoma wątkami oczekującymi na nawzajem zwolnienie zasobu. Bezpośrednio z examples.oreilly.com/jenut/Deadlock.java
public class Deadlock { public static void main(String[] args) { // These are the two resource objects we'll try to get locks for final Object resource1 = "resource1"; final Object resource2 = "resource2"; // Here's the first thread. It tries to lock resource1 then resource2 Thread t1 = new Thread() { public void run() { // Lock resource 1 synchronized(resource1) { System.out.println("Thread 1: locked resource 1"); // Pause for a bit, simulating some file I/O or something. // Basically, we just want to give the other thread a chance to // run. Threads and deadlock are asynchronous things, but we're // trying to force deadlock to happen here... try { Thread.sleep(50); } catch (InterruptedException e) {} // Now wait 'till we can get a lock on resource 2 synchronized(resource2) { System.out.println("Thread 1: locked resource 2"); } } } }; // Here's the second thread. It tries to lock resource2 then resource1 Thread t2 = new Thread() { public void run() { // This thread locks resource 2 right away synchronized(resource2) { System.out.println("Thread 2: locked resource 2"); // Then it pauses, for the same reason as the first thread does try { Thread.sleep(50); } catch (InterruptedException e) {} // Then it tries to lock resource1. But wait! Thread 1 locked // resource1, and won't release it 'till it gets a lock on // resource2. This thread holds the lock on resource2, and won't // release it 'till it gets resource1. We're at an impasse. Neither // thread can run, and the program freezes up. synchronized(resource1) { System.out.println("Thread 2: locked resource 1"); } } } }; // Start the two threads. If all goes as planned, deadlock will occur, // and the program will never exit. t1.start(); t2.start(); } }
źródło
If all goes as planned, deadlock will occur, and the program will never exit.
Czy możemy zrobić ten przykładguarantee
impasu?Impas może nastąpić w sytuacji, gdy ktoś
Girl1
chce flirtowaćGuy2
, zostaje złapany przez innegoGirl2
iGirl2
chce flirtować zGuy1
złapanymGirl1
. Ponieważ obie dziewczyny czekają na siebie, stan ten nazywa się impasem.class OuchTheGirls { public static void main(String[] args) { final String resource1 = "Guy1"; final String resource2 = "Guy2"; // Girl1 tries to lock resource1 then resource2 Thread Girl1 = new Thread(() -> { synchronized (resource1) { System.out.println("Thread 1: locked Guy1"); try { Thread.sleep(100);} catch (Exception e) {} synchronized (resource2) { System.out.println("Thread 1: locked Guy2"); } } }); // Girl2 tries to lock Guy2 then Guy1 Thread Girl2 = new Thread(() -> { synchronized (resource2) { System.out.println("Thread 2: locked Guy2"); try { Thread.sleep(100);} catch (Exception e) {} synchronized (resource1) { System.out.println("Thread 2: locked Guy1"); } } }); Girl1.start(); Girl2.start(); } }
źródło
Problem producentów-konsumentów wraz z problemem filozofów kulinarnych jest prawdopodobnie tak prosty, jak się da. Ma też pseudokod, który to ilustruje. Jeśli są one zbyt skomplikowane dla nowicjusza, lepiej postaraj się je zrozumieć.
źródło
Wybierz najprostszy możliwy scenariusz, w którym może dojść do impasu podczas wprowadzania koncepcji do uczniów. Wymagałoby to co najmniej dwóch wątków i co najmniej dwóch zasobów (tak mi się wydaje). Celem jest zaprojektowanie scenariusza, w którym pierwszy wątek ma blokadę zasobu 1 i czeka na zwolnienie blokady zasobu 2, podczas gdy w tym samym czasie wątek drugi blokuje zasób 2 i czeka na blokada zasobu jeden do zwolnienia.
Nie ma znaczenia, jakie są podstawowe zasoby; dla uproszczenia możesz po prostu utworzyć z nich parę plików, w których oba wątki będą mogły zapisywać.
EDYCJA: Zakłada brak komunikacji między procesami poza posiadanymi blokadami.
źródło
Uważam, że nieco trudny do zrozumienia, czytając problem filozofów kulinarnych, impas IMHO jest w rzeczywistości związany z alokacją zasobów. Chciałbym podzielić się prostszym przykładem, w którym 2 pielęgniarki muszą walczyć o 3 elementy wyposażenia, aby wykonać zadanie. Chociaż jest napisane w Javie. Prosta metoda lock () jest tworzona w celu symulacji zachodzenia zakleszczenia, dzięki czemu można ją zastosować również w innym języku programowania. http://www.justexample.com/wp/example-of-deadlock/
źródło
Prosty przykład z https://docs.oracle.com/javase/tutorial/essential/concurrency/deadlock.html
Wynik:
Zrzut wątku:
źródło
Oto jeden prosty impas w Javie. Potrzebujemy dwóch zasobów do zademonstrowania impasu. W poniższym przykładzie jeden zasób to blokada klasy (za pomocą metody synchronizacji), a drugi to liczba całkowita „i”
public class DeadLock { static int i; static int k; public static synchronized void m1(){ System.out.println(Thread.currentThread().getName()+" executing m1. Value of i="+i); if(k>0){i++;} while(i==0){ System.out.println(Thread.currentThread().getName()+" waiting in m1 for i to be > 0. Value of i="+i); try { Thread.sleep(10000);} catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { Thread t1 = new Thread("t1") { public void run() { m1(); } }; Thread t2 = new Thread("t2") { public void run() { try { Thread.sleep(100);} catch (InterruptedException e) { e.printStackTrace(); } k++; m1(); } }; t1.start(); t2.start(); } }
źródło
public class DeadLock { public static void main(String[] args) { Object resource1 = new Object(); Object resource2 = new Object(); SharedObject s = new SharedObject(resource1, resource2); TestThread11 t1 = new TestThread11(s); TestThread22 t2 = new TestThread22(s); t1.start(); t2.start(); } } class SharedObject { Object o1, o2; SharedObject(Object o1, Object o2) { this.o1 = o1; this.o2 = o2; } void m1() { synchronized(o1) { System.out.println("locked on o1 from m1()"); synchronized(o2) { System.out.println("locked on o2 from m1()"); } } } void m2() { synchronized(o2) { System.out.println("locked on o2 from m2()"); synchronized(o1) { System.out.println("locked on o1 from m2()"); } } } } class TestThread11 extends Thread { SharedObject s; TestThread11(SharedObject s) { this.s = s; } public void run() { s.m1(); } } class TestThread22 extends Thread { SharedObject s; TestThread22(SharedObject s) { this.s = s; } public void run() { s.m2(); } }
źródło
Oto prosty impas w C #.
void UpdateLabel(string text) { lock(this) { if(MyLabel.InvokeNeeded) { IAsyncResult res = MyLable.BeginInvoke(delegate() { MyLable.Text = text; }); MyLabel.EndInvoke(res); } else { MyLable.Text = text; } } }
Jeśli pewnego dnia wywołasz to z wątku GUI, a inny wątek również to wywoła - możesz się zakleszczyć. Drugi wątek dostaje się do EndInvoke, czeka, aż wątek GUI wykona delegata podczas przytrzymywania blokady. Wątek GUI blokuje się na tym samym zamku, czekając, aż inny wątek go zwolni - czego nie zrobi, ponieważ wątek GUI nigdy nie będzie dostępny do wykonania delegata, na który czeka inny wątek. (oczywiście blokada tutaj nie jest ściśle potrzebna - podobnie jak EndInvoke, ale w nieco bardziej złożonym scenariuszu blokada może zostać nałożona przez dzwoniącego z innych powodów, powodując ten sam zakleszczenie).
źródło
źródło
źródło
źródło
Stworzyłem ultra prosty przykład roboczy DeadLock: -
W powyższym przykładzie 2 wątki wykonują zsynchronizowane metody dwóch różnych obiektów. Zsynchronizowana metoda A jest wywoływana przez obiekt threadDeadLockA, a zsynchronizowana metodaB jest wywoływana przez obiekt threadDeadLockB. W metodzie A przekazywana jest referencja do threadDeadLockB, aw metodzieB - do threadDeadLockA. Teraz każdy wątek próbuje zablokować inny obiekt. W metodzie A wątek, który trzyma blokadę na threadDeadLockA, próbuje uzyskać blokadę na obiekcie threadDeadLockB i podobnie w metodzieB wątek, który trzyma blokadę na threadDeadLockB, próbuje uzyskać blokadę na threadDeadLockA. W ten sposób oba wątki będą czekać wiecznie, tworząc impas.
źródło
Pozwólcie, że wyjaśnię to jaśniej na przykładzie mającym więcej niż 2 wątki.
Powiedzmy, że masz n wątków, z których każdy posiada odpowiednio zamki L1, L2, ..., Ln. Teraz powiedzmy, zaczynając od wątku 1, każdy wątek próbuje uzyskać blokadę wątku sąsiedniego. Tak więc, wątek 1 zostaje zablokowany przy próbie uzyskania L2 (ponieważ właścicielem L2 jest wątek 2), wątek 2 zostaje zablokowany dla L3 i tak dalej. Wątek n zostaje zablokowany dla L1. Jest to teraz impas, ponieważ żaden wątek nie jest w stanie wykonać.
W powyższym przykładzie widać, że istnieją trzy wątki zawierające
Runnable
zadania task1, task2 i task3. Przed instrukcjąsleep(100)
wątki uzyskują blokady trzech obiektów roboczych, gdy wchodzą docall()
metody (ze względu na obecnośćsynchronized
). Ale gdy tylko próbującallAnother()
złapać obiekt swojego sąsiada, zostają zablokowani, co prowadzi do impasu, ponieważ zamki tych obiektów zostały już zajęte.źródło
źródło
Podstępnym sposobem na zakleszczenie za pomocą tylko jednego wątku jest dwukrotna próba zablokowania tego samego (nierekurencyjnego) muteksu. Może to nie jest prosty przykład, którego szukałeś, ale z pewnością już napotkałem takie przypadki.
źródło
Oto mój szczegółowy przykład impasu po spędzeniu dużej ilości czasu. Mam nadzieję, że to pomoże :)
źródło