Bloqueos reentrantes en C #

119

¿El siguiente código dará como resultado un interbloqueo al usar C # en .NET?

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

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

    public void Bar()
    {
        lock(lockObj)
        { 
          // Do something 
        }
    }       
 }
Chico
fuente
6
¿Podríamos considerar cambiar el título de esta pregunta, quizás a algo como el recientemente cerrado ¿Por qué los bloqueos anidados no causan un interbloqueo? Tal como está, el título parece casi diseñado para evitar que la gente lo descubra.
Jeff Sternal
12
En realidad, encontré esto basado en la palabra de búsqueda 'reentrante', y respondió a mi pregunta. Si es una pregunta doble, ese es un problema diferente ...
emfurry
Estoy de acuerdo con el comentario de @JeffSternal: esta pregunta asume que la persona que busca la pregunta ya está familiarizada con los bloqueos de "reentrante". Otra pregunta de duplicación que creo que tenía un buen título para esto: stackoverflow.com/questions/3687505/…
Luis Perez

Respuestas:

148

No, no siempre que esté bloqueando el mismo objeto. El código recursivo efectivamente ya tiene el bloqueo y, por lo tanto, puede continuar sin obstáculos.

lock(object) {...}es una abreviatura del uso de la clase Monitor . Como señala Marc , Monitorpermite el reentrada , por lo que los intentos repetidos de bloquear un objeto en el que el hilo actual ya tiene un bloqueo funcionarán bien.

Si comienzas a bloquear diferentes objetos, es cuando debes tener cuidado. Preste especial atención a:

  • Adquiera siempre bloqueos en un número determinado de objetos en la misma secuencia.
  • Siempre suelte los candados en la secuencia inversa a la forma en que los adquirió.

Si rompes cualquiera de estas reglas, tienes prácticamente la garantía de que tendrás problemas de interbloqueo en algún momento .

Aquí hay una buena página web que describe la sincronización de subprocesos en .NET: http://dotnetdebug.net/2005/07/20/monitor-class-avoiding-deadlocks/

Además, bloquee la menor cantidad posible de objetos a la vez. Considere la posibilidad de aplicar cerraduras de grano grueso siempre que sea posible. La idea es que si puede escribir su código de manera que haya un gráfico de objeto y pueda adquirir bloqueos en la raíz de ese gráfico de objeto, hágalo. Esto significa que tiene un bloqueo en ese objeto raíz y, por lo tanto, no tiene que preocuparse tanto por la secuencia en la que adquiere / libera bloqueos.

(Una nota más, su ejemplo no es técnicamente recursivo. Para que sea recursivo, Bar()debería llamarse a sí mismo, normalmente como parte de una iteración).

Neil Barnwell
fuente
1
Especialmente en diferentes secuencias.
Marc Gravell
6
Re recursividad; en efecto; para beneficio de Guy, el término es reentrante
Marc Gravell
Gracias por la aclaración sobre la terminología. He editado y corregido la pregunta.
Guy
Esta pregunta parece recibir bastante atención, así que actualicé mi respuesta con un par de otras notas que se me ocurrieron desde que la escribí por primera vez.
Neil Barnwell
En realidad, no creo que importe el orden en el que sueltas las cerraduras. El orden en el que los elijas definitivamente lo hace, pero siempre que liberar el bloqueo no esté condicionado a nada (puedes liberarlo en cualquier momento), deberías estar bien siempre que liberes todos los bloqueos que has adquirido.
bobroxsox
20

Bueno, Monitorpermite la reentrada, por lo que no puede bloquearse usted mismo ...

Marc Gravell
fuente
7

Si un hilo ya está bloqueado, entonces no se bloqueará. El marco .Net garantiza esto. Solo debe asegurarse de que dos subprocesos no intenten adquirir los mismos dos bloqueos fuera de secuencia mediante cualquier ruta de código.

El mismo hilo puede adquirir el mismo candado varias veces, pero debes asegurarte de liberar el candado la misma cantidad de veces que lo adquieres. Por supuesto, siempre que utilice la palabra clave "bloquear" para lograr esto, sucede automáticamente.

Jeffrey L. Whitledge
fuente
Tenga en cuenta que eso es cierto para los monitores, pero no necesariamente para otros tipos de bloqueo.
Jon Skeet
(No quiero dar a entender que no sabías eso, por supuesto, solo que es una distinción importante :)
Jon Skeet
Ese es un buen punto. De hecho, iba a cambiar "bloqueo" a "monitor" en todo momento, pero luego me distraí. Y vago. Y el comportamiento también es cierto para los objetos kernal mutex de Windows, así que pensé, ¡lo suficientemente cerca!
Jeffrey L Whitledge
5

No, este código no tendrá bloqueos muertos. Si realmente desea crear un punto muerto, el más simple requiere al menos 2 recursos. Considere el escenario del perro y los huesos. 1. Un perro tiene control total sobre 1 hueso, por lo que cualquier otro perro tiene que esperar. 2. Se requieren 2 perros con 2 huesos como mínimo para crear un punto muerto cuando bloquean sus huesos respectivamente y buscan otros huesos también.

.. así sucesivamente n perros y m huesos y provocan interbloqueos más sofisticados.

Rishabh Jain
fuente