2

i'm newbie in c#. I need to obtain lock in 2 methods, but release in one method. Will that work?

public void obtainLock() {
    Monitor.Enter(lockObj);
}

public void obtainReleaseLock() {
    lock (lockObj) {
        doStuff
    }
}

Especially can I call obtainLock and then obtainReleaseLock? Is "doubleLock" allowed in C#? These two methods are always called from the same thread, however lockObj is used in another thread for synchronization.

upd: after all comments what do you think about such code? is it ideal?

public void obtainLock() {
    if (needCallMonitorExit == false) {
        Monitor.Enter(lockObj);
        needCallMonitorExit = true;
    }
    // doStuff
}

public void obtainReleaseLock() {
    try {
        lock (lockObj) {
            // doAnotherStuff
        }
    } finally {
        if (needCallMonitorExit == true) {
            needCallMonitorExit = false;
            Monitor.Exit(lockObj);
        }
    }
}
Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
Oleg Vazhnev
  • 23,239
  • 54
  • 171
  • 305

4 Answers4

2

Yes, locks are "re-entrant", so a call can "double-lock" (your phrase) the lockObj. however note that it needs to be released exactly as many times as it is taken; you will need to ensure that there is a corresponding "ReleaseLock" to match "ObtainLock".

I do, however, suggest it is easier to let the caller lock(...) on some property you expose, though:

 public object SyncLock { get { return lockObj; } }

now the caller can (instead of obtainLock()):

lock(something.SyncLock) {
     //...
}

much easier to get right. Because this is the same underlying lockObj that is used internally, this synchronizes against either usage, even if obtainReleaseLock (etc) is used inside code that locked against SyncLock.


With the context clearer (comments), it seems that maybe Wait and Pulse are the way to do this:

void SomeMethodThatMightNeedToWait() {
    lock(lockObj) {
        if(needSomethingSpecialToHappen) {
            Monitor.Wait(lockObj);
            // ^^^ this ***releases*** the lock (however many times needed), and
            // enters the pending-queue; when *another* thread "pulses", it
            // enters the ready-queue; when the lock is *available*, it
            // reacquires the lock (back to as many times as it held it 
            // previously) and resumes work
        }
        // do some work, happy that something special happened, and
        // we have the lock
    }
}
void SomeMethodThatMightSignalSomethingSpecial() {
    lock(lockObj) {
        // do stuff
        Monitor.PulseAll(lockObj);
        // ^^^ this moves **all** items from the pending-queue to the ready-queue
        // note there is also Pulse(...) which moves a *single* item
    }
}

Note that when using Wait you might want to use the overload that accepts a timeout, to avoid waiting forever; note also it is quite common to have to loop and re-validate, for example:

lock(lockObj) {
    while(needSomethingSpecialToHappen) {
        Monitor.Wait(lockObj);
        // at this point, we know we were pulsed, but maybe another waiting
        // thread beat us to it! re-check the condition, and continue; this might
        // also be a good place to check for some "abort" condition (and
        // remember to do a PulseAll() when aborting)
    }
    // do some work, happy that something special happened, and we have the lock
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • I need to obtain a lock in two methods, but release in one method. `lock {}` statement is not "cross-method" – Oleg Vazhnev Sep 27 '11 at 19:54
  • 2
    @javapowered er... *why* do you need to do that? genuine question - that sounds a bit of a code smell to me. There is probably a better way of doing what you want. But to be explicit: using `lock` will increment the counter once and decrement it once; it won't *wipe* the lock - you will need to explicitly call `Exit` one more time. – Marc Gravell Sep 27 '11 at 19:57
  • i need that because when ONE condition occured another thread should be blocked until ANOTHER condition occured. in addition ANOTHER condition itself should obtain a lock during processing – Oleg Vazhnev Sep 27 '11 at 20:02
  • @javapowered or perhaps better: use `Montior.Wait` (from the thread that needs to wait) and `Monitor.Pulse` (from the signalling thread); I'll add an example. – Marc Gravell Sep 27 '11 at 20:05
  • thanks, waiting for an example :) note i don't have signaling thread. both threads are equal... if another thread calls `lock (lockObj)` my two methods (in the description above) should wait for lock releasing – Oleg Vazhnev Sep 27 '11 at 20:11
  • @javapowered the example is there – Marc Gravell Sep 27 '11 at 20:14
  • pretty diffucult for me, still trying to understand... why not just add one more `Monitor.Exit` to my initial example and catch&ignore `SynchronizationLockException`... ? – Oleg Vazhnev Sep 27 '11 at 20:24
  • probably it would be easier just to add boolean flag, i update description to include updated code... – Oleg Vazhnev Sep 27 '11 at 20:47
1

Only one owner can hold the lock at a given time; it is exclusive. While the locking can be chained the more important component is making sure you obtain and release the proper number of times, avoiding difficult to diagnose threading issues.

When you wrap your code via lock { ... }, you are essentially calling Monitor.Enter and Monitor.Exit as scope is entered and departed.

When you explicitly call Monitor.Enter you are obtaining the lock and at that point you would need to call Monitor.Exit to release the lock.

Aaron McIver
  • 24,527
  • 5
  • 59
  • 88
  • 1
    it seems my question is dup of this one http://stackoverflow.com/questions/391913/re-entrant-locks-in-c so answer is `yes` my code is correct. But keeping question open probably someone can suggest better solution (or my solution is already pretty good...) – Oleg Vazhnev Sep 27 '11 at 19:49
  • 1
    True, but seems unrelated to the question; the question seems more focused on re-entrancy? – Marc Gravell Sep 27 '11 at 19:50
  • @javapowered: Please see my answer. Your code is NOT correct, it doesn't do what you expect it to do. – Daniel Hilgarth Sep 27 '11 at 19:56
  • Thanks, i see, however still not clear how to fix that easily. I can introduce `boolean` field to store if I need to call `Monitor.Exit` but this is not very elegant :) – Oleg Vazhnev Sep 27 '11 at 20:03
1

You would have to use the Monitor for this functionality. Note that you open yourself up to deadlocks and race conditions if you aren't careful with your locks and having them taken and released in seperate areas of code can be risky

 Monitor.Exit(lockObj);
Ian Dallas
  • 12,451
  • 19
  • 58
  • 82
1

This doesn't work.

The code

lock(lockObj)
{
    // do stuff
}

is translated to something like

Monitor.Enter(lockObj)
try
{
    // do stuff
}
finally
{
    Monitor.Exit(lockObj)
}

That means that your code enters the lock twice but releases it only once. According to the documentation, the lock is only really released by the thread if Exit was called as often as Enter which is not the case in your code.
Summary: Your code will not deadlock on the call to obtainReleaseLock, but the lock on lockObj will never be released by the thread. You would need to have an explicit call to Monitor.Exit(lockObj), so the calls to Monitor.Enter matches the number of calls to Monitor.Exit.

Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
  • pedant point; in C# 4 the exact translation can be a bit different, depending on the target platform and the `Monitor` overloads available. – Marc Gravell Sep 27 '11 at 19:53
  • @MarcGravell: Can you elaborate? I didn't find anything in the documentation – Daniel Hilgarth Sep 27 '11 at 19:55
  • What should I do if I need 2 entrance point in the lock and 1 exit point? I do not want to "track" now many times I've entered to call `Exit` once or twice – Oleg Vazhnev Sep 27 '11 at 19:58
  • @Daniel yes, I've complained to Eric and Mads; apparently it is fixed for the next drop of the documentation; [see Eric's blog](http://blogs.msdn.com/b/ericlippert/archive/2009/03/06/locks-and-exceptions-do-not-mix.aspx), which shows what the 4.0 compiler tries to do. There are also subtle changes to field-like events - I can't remember if those made it into the docs; see Chris Burrows' blog for that (3-parter). – Marc Gravell Sep 27 '11 at 19:59
  • @MarcGravell: Thanks! However, in our case here, this is not really relevant, right? – Daniel Hilgarth Sep 27 '11 at 20:08
  • @Daniel: Right, the small difference in codegen is not relevant to the user's actual question. Though it is of course handy to know. – Eric Lippert Sep 27 '11 at 20:14
  • @Daniel well, it *is*, because your example of "is translated to" is not accurate for C# 4 targeting .NET 4. – Marc Gravell Sep 27 '11 at 20:14
  • 2
    @Eric hey! I did say it was a pedantic point! (I swear sometimes you just have to think Eric's name and he appears...) – Marc Gravell Sep 27 '11 at 20:17
  • @MarcGravell: I added "something like", now it doesn't claim to be accurate :-) – Daniel Hilgarth Sep 27 '11 at 20:31