Under what conditions can a thread enter a lock (Monitor) region more than once concurrently?

前端 未结 6 1914
一个人的身影
一个人的身影 2021-01-05 04:45

(question revised): So far, the answers all include a single thread re-entering the lock region linearly, through things like recursion, where you can trace the steps of a s

6条回答
  •  轻奢々
    轻奢々 (楼主)
    2021-01-05 05:12

    IMHO, Re-entering a lock is not something you need to take care to avoid (given many people's mental model of locking this is, at best, dangerous, see Edit below). The point of the documentation is to explain that a thread cannot block itself using Monitor.Enter. This is not always the case with all synchronization mechanisms, frameworks, and languages. Some have non-reentrant synchronization in which case you have to be careful that a thread doesn't block itself. What you do need to be careful about is always calling Monitor.Exit for every Monitor.Enter call. The lock keyword does this for you automatically.

    A trivial example with re-entrance:

    private object locker = new object();
    
    public void Method()
    {
      lock(locker)
      {
        lock(locker) { Console.WriteLine("Re-entered the lock."); }
      }
    }
    

    The thread has entered the lock on the same object twice so it must be released twice. Usually it is not so obvious and there are various methods calling each other that synchronize on the same object. The point is that you don't have to worry about a thread blocking itself.

    That said you should generally try to minimize the amount the time you need to hold a lock. Acquiring a lock is not computationally expensive, contrary to what you may hear (it is on the order of a few nanoseconds). Lock contention is what is expensive.

    Edit

    Please read Eric's comments below for additional details, but the summary is that when you see a lock your interpretation of it should be that "all activations of this code block are associated with a single thread", and not, as it is commonly interpreted, "all activations of this code block execute as a single atomic unit".

    For example:

    public static void Main()
    {
      Method();
    }
    
    private static int i = 0;
    private static object locker = new object();
    public static void Method()
    {
      lock(locker)
      {
        int j = ++i;
    
        if (i < 2)
        {
          Method();
        }
    
        if (i != j)
        {
          throw new Exception("Boom!");
        }
      }
    }
    

    Obviously, this program blows up. Without the lock, it is the same result. The danger is that the lock leads you into a false sense of security that nothing could modify state on you between initializing j and evaluating the if. The problem is that you (perhaps unintentionally) have Method recursing into itself and the lock won't stop that. As Eric points out in his answer, you might not realize the problem until one day someone queues up too many actions simultaneously.

提交回复
热议问题