1

I'm confused by the example given by Leo Davidson in is Ccriticalsection usable in production?. Leo gives three code blocks introduced as "Wrong (his example)", "Right", and "Even better (so you get RAII)".

After dismissing the first block as "Wrong", Leo acknowledges later that this is something that can occur if a function that obtains a lock calls another function which obtains the same lock. Fine - there is a real danger here to avoid, and the example is not so much "wrong" as an easy trap to fall into through careless programming.

But the second and third examples confuse me completely... because we have one sync object (the CCriticalSection crit) which is used for two CSingleLock locks... implying that crit is not a lockable thing at all, but only the mechanism which does the locking for an independent object or objects. The trouble is, there is a comment saying "crit is unlocked now" right at the end... which contradicts that implication. Also... other comments qualify themselves by the need to test IsLocked()... when in my understanding, the CCriticalSection cannot timeout, and will only ever return if IsLocked() is TRUE.

The Microsoft documentation I have scanned is really not clear about what role the CSyncObject plays and the CSingleLock or CMultiLock plays. That's my main concern. Can anyone point to documentation that definitively says you can create two locks using a single sync object as Leo has suggested here?

Community
  • 1
  • 1
omatai
  • 3,448
  • 5
  • 47
  • 74

1 Answers1

1

After dismissing the first block as "Wrong", Leo acknowledges later that this is something that can occur if a function that obtains a lock calls another function which obtains the same lock. Fine - there is a real danger here to avoid, and the example is not so much "wrong" as an easy trap to fall into through careless programming.

The "wrong" first block is always wrong and should never be something you do, whether explicitly or by accident. You cannot use a CSingleLock to obtain multiple locks at the same time.

As its name suggests, CSingleLock is an object which manages one lock on one synchronization object. (The underlying synchronization object may be capable of being locked multiple times, but not via just a single CSingleLock.)

I meant that the other two code-blocks were situations you could run into legitimately.

You never need to lock the same CCriticalSection if you already have a lock on it (since you only need one lock to know you own the object), but you may lock it multiple times (usually as a result of holding the lock, then calling a function which gets the lock itself in case it is called by something that doesn't already have it). That's fine.

But the second and third examples confuse me completely... because we have one sync object (the CCriticalSection crit) which is used for two CSingleLock locks... implying that crit is not a lockable thing at all, but only the mechanism which does the locking for an independent object or objects.

You can lock a CCriticalSection directly (and multiple times if you want to). It has Lock and Unlock methods for doing that.

If you do that, though, you have to ensure that you have matching Unlock calls for every one of your Lock calls. It can be easy to miss one (especially if you use early returns or exceptions where an Unlock later in a function may be bypassed entirely).

Using a CSingleLock to lock a CCriticalSection is usually better because it will release the lock it holds automatically when it goes out of scope (including if you return early, throw an exception or whatever).

Can anyone point to documentation that definitively says you can create two locks using a single sync object as Leo has suggested here?

Although I couldn't find the source, CCriticalSection (like most MFC objects) is almost certainly a very thin wrapper around the Win32 equivalent, in this case CRITICAL_SECTION. The documentation on EnterCriticalSection tells you:

After a thread has ownership of a critical section, it can make additional calls to EnterCriticalSection or TryEnterCriticalSection without blocking its execution. This prevents a thread from deadlocking itself while waiting for a critical section that it already owns. The thread enters the critical section each time EnterCriticalSection and TryEnterCriticalSection succeed. A thread must call LeaveCriticalSection once for each time that it entered the critical section.

Leo Davidson
  • 6,093
  • 1
  • 27
  • 29
  • Thanks... but maybe my point was not clear. If CCriticalSection is an object that you can lock and unlock - and which therefore holds the state of whether something is locked or not... then no amount of wrapping it will let you use the lock twice as your code suggests. Once it is locked, it is locked, and you can't lock it again using another wrapper object. And once it is unlocked through the first wrapper object, then any code prior to the other unlock on the second wrapper object is not locked at all, surely? – omatai Dec 13 '10 at 04:22
  • I suspect we both could have made ourselves clearer here. I think I see where you're coming from, but I wonder whether the code actually works in practice. In the second and third cases, I imagine the second lock will deadlock on itself. I agree that the first case is something one should never do... but I can't help thinking the other cases are both something one should never do either. – omatai Dec 13 '10 at 04:33
  • CCriticalSection *can* be locked multiple times. (Only the thread which got the first lock can lock it additional times, until that thread releases all of its locks.) CSingleLock can only be used to manage one lock at a time. Multiple CSingleLock instances can be used to manage multiple locks at once. The important thing to realise is that all CSingleLock is doing is calling Lock and Unlock on the CCriticalSection, and the value CSingleLock adds is that, when it goes out of scope, it will *automatically* call Unlock on the CCriticalSection (if it had previously called Lock on it). – Leo Davidson Dec 13 '10 at 10:20
  • OK - that's distinctly unintuitive behaviour... and a good incentive for me to move to the boost threading mechanisms, which I understand have (or are about to become) part of the C++ standard. – omatai Dec 13 '10 at 22:37
  • What is unintuitive about it? If critical sections locked-up if the thread that already owned them asked for another lock then that would not be very useful and would make using them a nightmare. What good would that serve? It would just mean that threads could deadlock themselves. The whole point of a critical section is to ensure that one thread can use a resource without other threads interfering with it at the same time. Critical sections are not meant to protect the owning thread from itself (whatever that would actually mean). – Leo Davidson Dec 13 '10 at 22:47
  • To put it another way, when a thread acquires (locks) a critical section it locks out all other threads. That lasts until the owning thread has released all of its locks on the critical section, at which point another thread (or the same one again) may become the new owner and lock-out all other threads. – Leo Davidson Dec 13 '10 at 22:49
  • 1
    Think of it like a bathroom door that has multiple locks on it. If you go into the bathroom and turn the first lock on the door, nobody else can enter the bathroom. If you then turn the second lock on the door, nothing much changes because the door was already locked. After you are done and unlock both locks other people are then free to enter the bathroom. – Leo Davidson Dec 13 '10 at 22:51