views:

225

answers:

7

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 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.

+3  A: 

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.

Mehrdad Afshari
A: 

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

Fakrudeen
The people who downvoted can explain? He is asking the reason for stackoverflow [no pun intended]. It will happen irrespective of the lock with that code. So lock is red herring.
Fakrudeen
It is obvious his code is there to demonstrate the problem. He THOUGHT it should not recurse but block at the recursion.
TomTom
it is not obvious. >>>the reason behind the behaviour of the following code.behaviour = stackoverflowreason=no boundary case.If he cared only about reentry of lock, he could have used lock(obj);lock(obj); or f(){lock(obj){}; g();} g(lock(obj){}); Why introduce recursion [that too with no boundary case] as red herring.Also look at the replies of others. Many of them have the same confusion.
Fakrudeen
+5  A: 

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 internalyl increases the lock counter. Becasue 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

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 "ANOTHER" thread stuff.

TomTom
A: 

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.

Paul Alexander
A: 

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.

hallie
+1  A: 

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);
    }
}
Steven
+5  A: 

Please not 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, because strings are immutable, they are shared 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();
Steven
+1 for being the only one to pick this up. Strings can be interned, which means weird behavior.
Richard Szalay