Blokady ponownego wejścia w języku C #

119

Czy poniższy kod spowoduje zakleszczenie przy użyciu języka C # na platformie .NET?

 class MyClass
 {
    private object lockObj = new object();

    public void Foo()
    {
        lock(lockObj)
        { 
             Bar();
        }
    }

    public void Bar()
    {
        lock(lockObj)
        { 
          // Do something 
        }
    }       
 }
Chłopak
źródło
6
Czy możemy rozważyć zmianę tytułu tego pytania - być może na coś takiego, jak ostatnio zamknięte Dlaczego zamki zagnieżdżone nie powodują impasu? W obecnym kształcie tytuł wydaje się prawie zaprojektowany tak, aby ludzie go nie odkryli.
Jeff Sternal,
12
Właściwie znalazłem to na podstawie wyszukiwanego słowa „reentrant” i odpowiedź na moje pytanie. Jeśli jest to drugie pytanie, to inna sprawa ...
emfurry,
Zgadzam się z komentarzem @JeffSternal w tym pytaniu zakłada się, że osoba wyszukująca pytanie zna już blokady „Re-entrant”. Kolejne pytanie o powielanie, które moim zdaniem miało dobry tytuł do tego: stackoverflow.com/questions/3687505/…
Luis Perez

Odpowiedzi:

148

Nie, pod warunkiem, że blokujesz ten sam obiekt. Kod rekurencyjny skutecznie już posiada blokadę i może działać bez przeszkód.

lock(object) {...}jest skrótem do używania klasy Monitor . Jak wskazuje Marc , Monitorpozwala na ponowne wejście , więc wielokrotne próby zablokowania obiektu, na którym bieżący wątek ma już blokadę, będą działać dobrze.

Jeśli zaczniesz blokować różne obiekty, wtedy musisz być ostrożny. Zwróć szczególną uwagę na:

  • Zawsze ustawiaj blokady na określonej liczbie obiektów w tej samej kolejności.
  • Zawsze zwalniaj blokady w odwrotnej kolejności do sposobu ich zdobycia.

Jeśli złamiesz którąkolwiek z tych zasad, masz prawie gwarancję, że w pewnym momencie wystąpią problemy z zakleszczeniem .

Oto jedna dobra strona internetowa opisująca synchronizację wątków w .NET: http://dotnetdebug.net/2005/07/20/monitor-class-avoiding-deadlocks/

Ponadto blokuj jak najmniej obiektów naraz. W miarę możliwości rozważ zastosowanie gruboziarnistych zamków . Chodzi o to, że jeśli możesz napisać swój kod w taki sposób, że istnieje graf obiektowy i możesz uzyskać blokady w katalogu głównym tego grafu obiektowego, zrób to. Oznacza to, że masz jedną blokadę na tym obiekcie root i dlatego nie musisz się tak bardzo martwić o kolejność, w której nabywasz / zwalniasz blokady.

(Jeszcze jedna uwaga, twój przykład nie jest technicznie rekurencyjny. Aby był rekurencyjny, Bar()musiałby wywołać siebie, zwykle jako część iteracji.)

Neil Barnwell
źródło
1
Zwłaszcza w różnych sekwencjach.
Marc Gravell
6
Rekurencja; w rzeczy samej; na korzyść Guya, termin jest powtórny
Marc Gravell
Dziękuję za wyjaśnienie terminologii - zredagowałem i poprawiłem pytanie.
Guy
Wydaje się, że to pytanie przyciąga sporo uwagi, więc zaktualizowałem swoją odpowiedź kilkoma innymi uwagami, które wymyśliłem, odkąd je napisałem.
Neil Barnwell
Właściwie nie sądzę, aby kolejność zwalniania blokad miała znaczenie. Kolejność, w jakiej je wybierasz, zdecydowanie tak jest, ale o ile zwolnienie blokady nie jest od niczego uzależnione (możesz zwolnić w dowolnym momencie), powinno być dobrze, o ile zwolnisz wszystkie zdobyte blokady.
bobroxsox
20

Cóż, Monitorpozwala na ponowne entuzjazm, więc nie możesz się zablokować ... więc nie: nie powinno

Marc Gravell
źródło
7

Jeśli wątek już trzyma blokadę, nie będzie się blokował. Zapewnia to framework .Net. Musisz tylko upewnić się, że dwa wątki nie próbują uzyskać tych samych dwóch blokad poza kolejnością przez jakiekolwiek ścieżki kodu.

Ten sam wątek może wielokrotnie uzyskać tę samą blokadę, ale musisz upewnić się, że zwalniasz blokadę taką samą liczbę razy, jak ją zdobywasz. Oczywiście, o ile używasz do tego słowa kluczowego „lock”, dzieje się to automatycznie.

Jeffrey L Whitledge
źródło
Zauważ, że dotyczy to monitorów, ale niekoniecznie innych rodzajów blokad.
Jon Skeet
(Nie chcę sugerować, że tego nie wiedziałeś, oczywiście - tylko, że jest to ważne rozróżnienie :)
Jon Skeet,
Trafne spostrzeżenie. Właściwie zamierzałem zmienić „blokadę” na „monitor”, ale potem się rozproszyłem. I leniwy. Zachowanie to jest również prawdziwe dla obiektów jądra muteksów systemu Windows, więc pomyślałem, że wystarczająco blisko!
Jeffrey L Whitledge,
5

Nie, ten kod nie będzie miał martwych zamków. Jeśli naprawdę chcesz stworzyć zakleszczenie, najprostszy wymaga co najmniej 2 zasobów. Rozważ scenariusz dotyczący psa i kości. 1. Pies ma pełną kontrolę nad 1 kością, więc każdy inny pies musi czekać. 2. Wymagane jest minimum 2 psów z 2 kośćmi, aby doprowadzić do impasu, gdy odpowiednio blokują swoje kości i szukają kości innych.

... tak dalej i tak dalej n psów i m kości i powodują bardziej wyrafinowane zakleszczenia.

Rishabh Jain
źródło