Zakleszczenie, to taka sytuacja gdzie jeden wątek czeka na zakończenie drugiego wątku, aby otrzymać dostęp do sekcji krytycznej, a ten drugi czeka na zakończenie tego pierwszego.
Przeanalizujmy poniższy kod:
class Program { private static readonly object lockA = new object(); private static readonly object lockB = new object(); static void Main(string[] args) { Console.WriteLine("Let's get the started. We are in main thread"); Task.Factory.StartNew(RiseDeadlock); // #01 Console.WriteLine("We are before lockB in main thread."); lock (lockB) // #04 { Console.WriteLine("We are in lockB in main thread."); Console.WriteLine("Simulate long running instruction in main thread."); Thread.Sleep(5000); // #05 Console.WriteLine("The long running instruction has been ended in main thread."); Console.WriteLine("We are waiting for release of lockA in main thread."); lock (lockA) // #06 { Console.WriteLine("This code will never be executed in main thread."); } } Console.ReadLine(); } private static void RiseDeadlock() { Console.WriteLine("RiseDeadlock method hes been started in new thread."); Console.WriteLine("We are before lockA in RiseDeadlock method."); lock (lockA) // #02 { Console.WriteLine("We are in lockA in RiseDeadlock method."); Console.WriteLine("Simulate long running instruction in RiseDeadlock method."); Thread.Sleep(10000); // #03 Console.WriteLine("The long running instruction has been ended in RiseDeadlock method."); Console.WriteLine("We are in lockA before lockB in RiseDeadlock method."); Console.WriteLine("We are waiting for release of lockB in RiseDeadlock method."); lock (lockB) // # 07 { Console.WriteLine("This code will never be executed in RiseDeadlock method."); } } } }
#01
Główny wątek tworzy nowy wątek poboczny, w którym wywołuje metodę RiseDeadlock
.
#02
Zakładamy blokadę lockA
w metodzie RiseDeadlock
. Od tego momentu dostęp do tej sekcji kodu będzie miał tylko jeden wątek. Inne wątki będą czekać na dostęp aż do momentu, gdy aktualny wątek nie wykona wszystkich operacji i nie opuści tej sekcji kodu. Obecnie w tej sekcji krytycznej znajduje się ten nowo utworzony poboczny wątek, powstały na potrzeby wywołania metody RiseDeadlock
.
#03
Symulujemy długotrwałe obliczenia, które potrwają około 10 sekund.
#04
Równolegle z wykonywaniem kodu opisanego w punkcie #02 i #03 główny wątek, w metodzie Main
założył blokadę lockB
. Tutaj analogicznie — jak to jest opisane w punkcie #02 — od teraz dostęp do tej sekcji krytycznej posiada wątek główny. Inne wątki będą czekały na swoją kolej, aż ten wątek opuści tę sekcję kodu.
Obecnie mamy sytuację, w której dwa wątki mają założone swoje blokady. Wątek główny założył blokadę lockB
a wątek poboczny założył blokadę lockA
.
#05
W głównym wątku, w metodzie Main
, znajdując się w sekcji krytycznej lockB
również symulujemy długotrwałe obliczenia, które potrwają około 5 sekund.
Symulacja tych długotrwałych obliczeń ma nam zagwarantować to, że wątek poboczny uprzedzi wątek główny i zdąży założyć blokadę lockA
, zanim wątek główny założyłby ją tuż po założeniu blokady lockB
.
Gdyby wątek główny w metodzie Main
zdążył założyć blokadę lockB
i lockA
, zanim wątek poboczny założyłby blokadę lockA
, wówczas nie doszłoby do zakleszczenia. Wątek główny wykonałby swój kod i zwolniłby blokady. Zaraz potem, wątek poboczny otrzymałby dostęp do tych sekcji krytycznych, założyłby swoje blokady lockA
i lockB
, wykonał swój kod, i również zwolniłby te sekcje krytyczne.
#06
To jeszcze nie zakleszczenie, ale jest bardzo blisko do tego. Wykonała się symulacja długotrwałych obliczeń opisana w punkcie poprzednim. Teraz główny wątek rozpoczyna czekanie aż wątek poboczny zwolni blokadę lockA
.
#07
Voilà. Normalnie to nie ma się z czego cieszyć, ale właśnie osiągnęliśmy zakleszczenie. Wątek poboczny zakończył swoje symulacje długotrwałych obliczeń i rozpoczął czekanie na zwolnienie blokady lockB
, która została założona przez wątek główny.
Żaden z tych wątków nie doczeka się na zwolnienie tych blokad. Każdy z tych wątków zwolniłby dostęp do sekcji krytycznej, gdyby otrzymał wstęp do kolejnej sekcji, ale nigdy to nie nastąpi. Wątki będą czekać na siebie w nieskończoność. Mamy klasyczne zakleszczenie.