Lock
to tak naprawdę System.Threading.Monitor
. Napiszmy, skompilujmy, zdekompilujmy i przeanalizujmy kod, w którym tworzymy sekcję krytyczną przy użyciu słówka kluczowego lock
.
class TeoVincent { private readonly object _lockObj = new object(); public void Method() { lock (_lockObj) { Console.WriteLine("critical section"); } } }
Po skompilowaniu oraz otwarciu pliku wynikowego *.exe przez ILSpy, dotPeak lub inne tego typu narzędzie otrzymamy taki kod IL:
.method public hidebysig instance void Method() cil managed { .maxstack 2 .locals init ( [0] object V_0, [1] bool V_1 ) // [10 9 - 10 10] IL_0000: nop // [11 13 - 11 28] IL_0001: ldarg.0 // this IL_0002: ldfld object TeoVincent.Monitor.Enter.TeoVincent::_lockObj IL_0007: stloc.0 // V_0 IL_0008: ldc.i4.0 IL_0009: stloc.1 // V_1 .try { IL_000a: ldloc.0 // V_0 IL_000b: ldloca.s V_1 IL_000d: call void [mscorlib]System.Threading.Monitor::Enter(object, bool&) IL_0012: nop // [12 13 - 12 14] IL_0013: nop // [13 17 - 13 55] IL_0014: ldstr "critical section" IL_0019: call void [mscorlib]System.Console::WriteLine(string) IL_001e: nop // [14 13 - 14 14] IL_001f: nop IL_0020: leave.s IL_002d } // end of .try finally { IL_0022: ldloc.1 // V_1 IL_0023: brfalse.s IL_002c IL_0025: ldloc.0 // V_0 IL_0026: call void [mscorlib]System.Threading.Monitor::Exit(object) IL_002b: nop IL_002c: endfinally } // end of finally // [15 9 - 15 10] IL_002d: ret } // end of method TeoVincent::Method
- linia 19: pojawił się blok
try{}
- linia 23: mamy wywołanie metody
System.Threading.Monitor::Enter(object, bool&)
- linia 38: mamy blok
finally{}
. - linia 44: mamy wywołanie metody
System.Threading.Monitor::Exit(object)
Jak widać, podczas kompilacji generowany jest kod IL, który odpowiada takiej implementacji:
class TeoVincent { private readonly object _lockObj = new object(); public void Method() { try { System.Threading.Monitor.Enter(_lockObj); Console.WriteLine("critical section"); } finally { System.Threading.Monitor.Exit(_lockObj); } } }
Przeanalizujmy teraz ten kod i zastanówmy się, czy jest on pozbawiony wad?
Jeśli wystąpi wyjątek w sekcji krytycznej wówczas trafiamy do bloku finally i zwolnimy zasób. Skoro powstał wyjątek, to możemy nie chcieć zwalniać sekcji krytycznej. Może lepszy byłby deadlock niż dalsze niszczenie danych przez wpuszczenie innego wątku.
Jeśli wyjątek wystąpi w metodzie Monitor.Enter, wówczas trafiamy do finally i wywołujemy metodę Monitor.Exit co spowoduje kolejny wyjątek.
Konstrukcja ta nie jest odporna na zakleszczenia. W szczególnych przypadkach mażemy chcieć sprawdzać czas, jaki dany wątek oczekuje na wejście do sekcji krytycznej. Po przekroczeniu zadanego czasu wątek odpuszcza sobie dalsze czekanie.
Rozwiązaniem problemów opisanych w punkcie dwóch ostatnich akapitach będzie kod:
class TeoVincent { private readonly object _lockObj = new object(); public void Method() { bool token = false; try { System.Threading.Monitor.TryEnter(_lockObj, new TimeSpan(0, 0, 0, 5), ref token); Console.WriteLine("critical section"); } finally { if (token) { System.Threading.Monitor.Exit(_lockObj); } } } }