开发者

Lock aqcuired and further attempts to lock do not block: are C# locks re-entrant?

I've written a test of what I think should be a valid case for a deadlock. It appears that once the lock has been acquired by an instance of the a class, that instance doesn't need to re-acquire the lock anymore even if I explicitly try to call another method that should lock again.

Here is the class:

internal class Tester
{
    private readonly object _sync = new object();

    public Tester() { }

    public void TestLock()
    {
        lock (_sync)
        {
            for (int i = 0; i < 10; i++)
            {
                Deadl开发者_C百科ock(i);
            }
        }

    }

    private void Deadlock(int i)
    {
        lock (_sync)
        {
            Trace.WriteLine(i + " no deadlock!");
        }
    }
}

Output:

0 no deadlock!

1 no deadlock!

2 no deadlock!

3 no deadlock!

4 no deadlock!

5 no deadlock!

6 no deadlock!

7 no deadlock!

8 no deadlock!

9 no deadlock!

I would have thought that this would cause a deadlock... can anybody shed some light on this?


Locks in .NET are reentrant. Only acquisitions from other threads are blocked. When the same thread locks the same object multiple times, it simply increments a counter, and decrements it when released. When the counter hits zero, the lock is actually released for access from other threads.


The Monitor, Mutex and ReaderWriterLock classes maintain locks that have thread affinity. The ReaderWriterLockSlim class lets you choose, it has a constructor that takes a LockRecursionPolicy value. Using LockRecursionPolicy.NoRecursion is an optimization, a fairly big one if your locking is really fine-grained.

The Semaphore class is a synchronization class that does not have any thread affinity. This code deadlocks reliably:

class Tester {
    private Semaphore sem = new Semaphore(1, 1);
    public void TestLock() {
        sem.WaitOne();
        for (int i = 0; i < 10; i++) Deadlock(i);
        sem.Release();
    }

    private void Deadlock(int i) {
        if (!sem.WaitOne(100)) Console.WriteLine("deadlock!");
        else {
            sem.Release();
            Console.WriteLine("No deadlock!");
        }
    }
}

In general, the thread affine synchronization classes require two threads and two locks to deadlock. The standard pattern is for one thread to acquire locks A and B, for the other to acquire B and A. The order is important.

There are less obvious deadlocks scenarios around in .NET programming, induced by locks that you cannot see because they are built-in to the .NET framework code. A very classic one is for BackgroundWorker. You could write code in the UI thread that spins on the Busy property, waiting for the BGW to complete. That always deadlocks when the BGW has a RunWorkerCompleted event handler. It cannot run until the UI thread goes idle, the BGW's Busy property won't be false until the event handler finished running.


In your scenario, you have a lock within another lock. Once the code hits the nested lock in "Deadlock", the "lock(...)" code is essentially ignored because it has already acquired it in "TestLock".

Great source for threading: http://www.albahari.com/threading/part2.aspx.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜