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

前端 未结 6 1920
一个人的身影
一个人的身影 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:02

    Let's think about something other than recursion.
    In some of business logics, they would like to control the behaviors of synchronization. One of these patterns, they invoke Monitor.Enter somewhere and would like to invoke Monitor.Exit elsewhere later. Here is the code to get the idea about that:

    public partial class Infinity: IEnumerable {
        IEnumerator IEnumerable.GetEnumerator() {
            return this.GetEnumerator();
        }
    
        public IEnumerator GetEnumerator() {
            for(; ; )
                yield return ~0;
        }
    
        public static readonly Infinity Enumerable=new Infinity();
    }
    
    public partial class YourClass {
        void ReleaseLock() {
            for(; lockCount-->0; Monitor.Exit(yourLockObject))
                ;
        }
    
        void GetLocked() {
            Monitor.Enter(yourLockObject);
            ++lockCount;
        }
    
        void YourParallelMethod(int x) {
            GetLocked();
            Debug.Print("lockCount={0}", lockCount);
        }
    
        public static void PeformTest() {
            new Thread(
                () => {
                    var threadCurrent=Thread.CurrentThread;
                    Debug.Print("ThreadId {0} starting...", threadCurrent.ManagedThreadId);
    
                    var intanceOfYourClass=new YourClass();
    
                    // Parallel.ForEach(Infinity.Enumerable, intanceOfYourClass.YourParallelMethod);
                    foreach(var i in Enumerable.Range(0, 123))
                        intanceOfYourClass.YourParallelMethod(i);
    
                    intanceOfYourClass.ReleaseLock();
    
                    Monitor.Exit(intanceOfYourClass.yourLockObject); // here SynchronizationLockException thrown
                    Debug.Print("ThreadId {0} finished. ", threadCurrent.ManagedThreadId);
                }
                ).Start();
        }
    
        object yourLockObject=new object();
        int lockCount;
    }
    

    If you invoke YourClass.PeformTest(), and get a lockCount greater than 1, you've reentered; not necessarily be concurrent.
    If it was not safe for reentrancy, you will get stuck in the foreach loop.
    In the code block where Monitor.Exit(intanceOfYourClass.yourLockObject) will throw you a SynchronizationLockException, it is because we are trying to invoke Exit more than the times it have entered. If you are about to use the lock keyword, you possibly would not encounter this situation except directly or indirectly of recursive calls. I guess that's why the lock keyword was provided: it prevents the Monitor.Exit to be omitted in a careless manner.
    I remarked the calling of Parallel.ForEach, if you are interested then you can test it for fun.

    To test the code, .Net Framework 4.0 is the least requirement, and following additional name spaces are required, too:

    using System.Threading.Tasks;
    using System.Diagnostics;
    using System.Threading;
    using System.Collections;
    

    Have fun.

提交回复
热议问题