views:

241

answers:

4

Is this pattern for mutual exclusion as safe as I think it is? If so, what do you call it?

lock (_lock) {
    if (_flag) return;
    else _flag = true;
}
try {
    //critical code...
}
finally {
    _flag = false;
}

I want to ensure the critical section, but without the other threads piling up waiting to acquire the lock. Obviously I make sure the flag is set nowhere else. Is there a better way?

+5  A: 

Have you looked at Monitor.TryEnter?

Roger Lipscombe
+13  A: 

No, that is not safe. If you want to ensure mutually exclusive without blocking, you can use Monitor.TryEnter:

if (Monitor.TryEnter(lockObj, 0)) {
    // got the lock !
    try {
        // code
    }
    finally { // release the lock
        Monitor.Exit(lockObj);
    }
}
Marc Gravell
That's fine, I will use TryEnter, but can you explain how the above is unsafe? Thanks.
marijne
Well, you are accessing the flag outside of the lock. As it happens, you're only setting it to false, but this means you could be denying an essential thread - I can foresee problems. Since it is relatively easy to use TryEnter instead, I'd use that...
Marc Gravell
A: 

Wouldn't a simple lock(Object) statement work? Behind the scenes, it creates the Monitor and critical section inside a try... finally block.

private static readonly Object lockMe = new Object();
lock(lockMe)
{
    // critical code
}
Anthony Mastrean
"but without the other threads piling up waiting to acquire the lock." - i.e. the OP wants it mutually exclusive but non-blocking.
Marc Gravell
That's an interesting requirement. I've never needed a locking mechanism like that.
Anthony Mastrean
+1  A: 

The correctness of your mutual exclusion pattern depends on the assignment _flag=false being atomic. Imagine what would happen if the assignment could be interrupted by another thread. If intermediate results of the assignment could be interpreted as false by the test, one assignment could cause several threads to enter the critical section.

The correctness of the mutual exclusion pattern also depends on the absence of optimisations in the compiler that may rearrange the ordering of the statements. Imagine a "smart" compiler that would move the assignment _flag=false up, because _flag is not referred to in the code that is in between (and the code in between does not throw exceptions). The compiler could then optimise the part in the lock section to read

if(_flag) return;

Both examples of why the pattern could fail are highly speculative and I think that you are safe in assuming it works. However, in the presence of another option that works as required, you're better off using that (see the other posts). If there are other developers in the same code, they do not need to consider whether the pattern works.

Renze de Waal
Interesting answer, thanks! I had not considered the effects of optimisation.
marijne