Czym jest na prawdę lock (lockObj) { } oraz jakie pociąga za sobą problemy

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);
            }
        }
    }
}

 

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj /  Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Wyloguj /  Zmień )

Zdjęcie na Facebooku

Komentujesz korzystając z konta Facebook. Wyloguj /  Zmień )

Połączenie z %s