开发者

Using lock(obj) inside a recursive call

开发者 https://www.devze.com 2022-12-22 03:33 出处:网络
As per my understanding a lock is not released until the runtime completes the code block of the lock(obj) ( because when the block completes开发者_StackOverflow it calls Monitor.Exit(obj).

As per my understanding a lock is not released until the runtime completes the code block of the lock(obj) ( because when the block completes开发者_StackOverflow it calls Monitor.Exit(obj).

With this understanding i am not able to understand the reason behind the behaviour of the following code :

private static string obj = "";
        private static void RecurseSome(int number)
        {
            Console.WriteLine(number);
            lock (obj)
            {
                RecurseSome(++number);
            }
        }

//Call: RecurseSome(0)

//Output: 0 1 2 3...... stack overflow exception

There must be some concept that i am missing. Please help.


A lock knows which thread locked it. If the same thread comes again it just increments a counter and does not block.

So, in the recursion, the second call also goes in - and the lock internally increases the lock counter - because it is the same thread (which already holds the lock).

ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_csref/html/656da1a4-707e-4ef6-9c6e-6d13b646af42.htm

Or MSDN: http://msdn.microsoft.com/en-us/library/c5kehkcz.aspx

states:

The lock keyword ensures that one thread does not enter a critical section of code while another thread is in the critical section. If another thread tries to enter a locked code, it will wait, block, until the object is released.

Note the thread references and the emphasis on "ANOTHER" thread.


Please do NOT lock on a string object. This could lead to unexpected behavior such as deadlocks in your application. You are currently locking on the empty string, which is even worse. The whole assembly is using the same empty string. And to make things worse; as an optimization, the CLR reuses strings over AppDomains. Locking on string means you are possibly doing a cross-domain lock.

Use the following code as lock object:

private readonly static object obj = new object();

UPDATE

In fact, I think it's safe to say that being allowed to lock on anything is a major design flaw in the .NET framework. Instead, they should have created some sort of SyncRoot sealed class and only allowed the lock statement and Monitor.Enter to accept instances of SyncRoot. This would have saved us a lot of misery. I do understand where this flaw is coming from though; Java has the same design.


As others already noted, the lock is helt by the thread and will therefore work. However, I want to add something to this.

Joe Duffy, a concurrency specialist from Microsoft, has multiple design rules about concurrency. One of his design rules state:

9 . Avoid lock recursion in your design. Use a non-recursive lock where possible.

Recursion typically indicates an over-simplification in your synchronization design that often leads to less reliable code. Some designs use lock recursion as a way to avoid splitting functions into those that take locks and those that assume locks are already taken. This can admittedly lead to a reduction in code size and therefore a shorter time-to-write, but results in a more brittle design in the end.

(source)

To prevent the recursive lock, rewrite the code to the following:

private readonly static object obj = new object();

private static void Some(int number)
{
    lock (obj)
    {
        RecurseSome(number);
    }
}

private static void RecurseSome(int number)
{
    Console.WriteLine(number);
    RecurseSome(++number);
}

Furthermore, I your code will throw a StackOverflowException, because it never ends recursively calling itself. You could rewrite your method as follows:

private static void RecurseSome(int number)
{
    Console.WriteLine(number);
    if (number < 100)
    {
        RecurseSome(++number);
    }
}


The lock is owned by the current thread. The recursive call is made on the current thread too. If another thread tries to acquire the lock, it'll block.


If you're asking about the stack overflow exception - it's because there's nothing in there to break from the recursion. The stack space is usually only a few K and you'll exhaust the space pretty quick.

Now the lock in this case can be used to serialize the output of the call so that if you call RecurseSome from two different threads, you'll see the entire list from the first thread, followed by the entire list from the second thread. Without the lock, the output from the two threads would be interleaved.

You could achieve the same results without taking the lock recursively by splitting the method:

private static void RecurseSome(int number)
{
    lock (obj)
    {
        RecurseSomeImp(number);
    }
}

private static void RecurseSomeImp(int number)
{
    Console.WriteLine(number);
    if( number < 100 ) // Add a boundary condition
        RecurseSomeImp(++number);
}

This will actually perform better as well as taking and releasing locks are fast, but not free.


It has nothing to do with lock. Check your recursion code. where is the boundary case to stop the recursion?


it is a continuous loop because there's no way to determine when to stop the recursion and the object that the thread is trying to access is always blocked.

0

精彩评论

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

关注公众号