Why doesn’t TryEnterCriticalSection try harder?

Bart wants to know why the Try­Enter­Critical­Section gives up if the critical section is busy instead of trying the number of times specified by the critical section spin count.

Actually, there was another condition on the proposed new behavior: "but does not release its timeslice to the OS if it fails to get it while spinning." This second condition is a non-starter because you can't prevent the operating system from taking your timeslice away from you. The best you can do is detect that you lost your previous timeslice when you receive the next one. And even that is expensive: You have to keep watching the CPU cycle counter, and if it jumps by too much, then you lost your timeslice. (And you might have lost it due to a hardware interrupt or paging. Good luck stopping those from happening.)

Even if there were a cheap way of detecting that the operating system was about to take your timeslice away from you, what good would it do? "Oh, my calculations indicate that if I spin one more time, I will lose my timeslice, so I'll just fail and return." Now the application regains control with 2 instructions left in its timeslice. That's not even enough time to test the return value and take a conditional jump! Even if the Try­Enter­Critical­Section managed to return just before the timeslice expired, that's hardly any consolation, because the timeslice is going to expire before the application can react to it. Whatever purpose there was to "up to the point where you're about to release the timeslice" is lost.

Okay, maybe the intention of that clause was "without intentionally releasing its timeslice (but if it loses its timeslice in the normal course of events, well that's the way the cookie crumbles)." That brings us back to the original question. Why doesn't Try­Enter­Critical­Section try harder? Well, because if it tried harder, then the people who didn't want it to try hard at all would complain that it tried too hard.

The function Try­Enter­Critical­Section may have been ambiguously named, because it doesn't describe how hard the function should try. Though in general, functions named TryXxx try only once, and that's the number of times Try­Enter­Critical­Section tries. Perhaps a clearer (but bulkier name) would have been Enter­Critical­Section­If­Not­Owned­By­Another­Thread.

The Try­Enter­Critical­Section function represents the core of the Enter­Critical­Section function. In pseudocode, the two functions work like this:

BOOL TryEnterCriticalSection(CRITICAL_SECTION *CriticalSection)
  atomically {
   if (CriticalSection is free or is owned by the current thread) {
     claim the critical section and return TRUE;
  return FALSE;

void EnterCriticalSection(CRITICAL_SECTION *CriticalSection)
 for (;;) {
  DWORD SpinTimes = 0;
  do {
    if (TryEnterCriticalSection(CriticalSection)) return;
  } while (++SpinTimes < GetSpinCount(CriticalSection));

The Try­Enter­Critical­Section function represents the smallest meaningful part of the Enter­Critical­Section process. If you want it to spin, you can write your own Try­Enter­Critical­Section­With­Spin­Count function:

BOOL TryEnterCriticalSectionWithSpinCount(
    CRITICAL_SECTION *CriticalSection,
    DWORD SpinCount)
  DWORD SpinTimes = 0;
  do {
    if (TryEnterCriticalSection(CriticalSection)) return TRUE;
  } while (++SpinTimes < SpinCount);
  return FALSE;

(Unfortunately, there is no Get­Critical­Section­Spin­Count function, so you'll just have to keep track of it yourself.)

Comments (23)
  1. w8 says:

    Why does EnterCriticalSection wait for critical section owner to leave?

    And why can TryEnterCriticalSection enter an already owned cs?

  2. Bill says:


    EnterCriticalSection waits for the owner to leave because the section is critical.

    TryEnterCriticalSection does not enter an already owned CS. It tries to enter it and fails. The return value lets the caller know it failed and that the caller had better not do whatever it is thinking about doing.

  3. Alexandre Grigoriev says:


    The CS state contains a ThreadId of the owner. If a CS is not owned, that field is some value that can’t be an id, like 0 or -1.

    Why does EnterCriticalSection wait for critical section owner to leave?

    Because that’s what its documentation says.

    And why can TryEnterCriticalSection enter an already owned cs?

    It can only enter it if this thread already owns it. Same as EnterCriticalSection.

    Although I think the application design should avoid using the reentrancy. It may make code maintenance very difficult, in the area of lock hierarchy and deadlock avoidance. If you acquire a CS recursively, that mostly happens because you call functions while holding it. And calling functions while holding a lock is something you should avoid.

  4. GWO says:

    Is it overly pessimistic that my first thought on reading the question was: "I bet this is going to be used for premature optimization".  

    I’d also bet the number of programmers playing with the spin count on their mutexes is massively higher than the number who’ve profiled the code sufficiently well to determine that lock-contention and semaphore overhead is actually a bottleneck…

  5. alex says:

    One common case when you use TryLock is when what you are trying to do is optional.  Ie, receiving the result of another thread that may or may not be done but will take awhile holding the lock to return the value.  If you are spinning in a loop doing useful work and checking for the result you don’t want to get blocked from doing work while the other thread is still holding the lock.  A simple example might be updating app statistics, not necessary for any particular step but should be done once in awhile.

  6. Anonymous says:

    Perhaps this is too much to ask, but maybe the people who ask a question like this should read a book on concurrent programming, attend a class on the subject at a decent university, or even read someone else’s source code or disassemble proprietary implementations, etc., before they start asking.  These things become much more apparent when you know what you’re talking about.

  7. WndSks says:

    http://msdn.microsoft.com/en-us/magazine/cc164040.aspx sort of documents and extends the CRITICAL_SECTION struct (I know MSDNMag != official docs)

  8. DodgyBob says:

    Maybe MS needs some more API calls:





    and so on.

  9. Worf says:

    @Anonymous: I remember our OS course. We drilled heavily on concurrent processing, doing all sorts of locks and stuff, and why we do it.

    Now I get a bit paranoid when doing re-entrant stuff with all the locks.

    Re-entrant locks are useful if you have helper functions that require grabbing the lock, and wrappers around those that encapsulate more functionality and also require the same lock. You can architect around it, but it can be annoying.

  10. Scott says:


    While you surely intended that for parody purposes, I’ve always thought you should stash a few fake features in your product to please demanding customers. Placebo features, if you will.

    Stuff your "Try­Enter­Critical­SectionABitHarder" in a DLL, have it sleep for 1ms, then call Try­Enter­Critical­Section. Don’t document the function, just leave it there. When some customer who clearly doesn’t know what they are doing comes demanding improvements, you can show them this "secret" undocumented solution. They will probably walk away pleased.

  11. -Sander1981- says:


    Now you have two functions to maintain…

  12. John says:


    More like:





  13. dave says:

    DontTryEnterCriticalSection would be an easier decoy function from the maintenance viewpoint.

  14. WndSks says:

    One could argue that there should be a EnterCriticalSectionHighPriority (In a multi reader single writer scenario where writes are important and infrequent)

    I have not really looked at the internals of how the CS works, so I’m not sure if it would be possible. Just inc’ing the refcount and forcing the threadowner to your own thread would cause issues with recursion for a thread that already had the lock. (I’m guessing threadowner is not verified on CS leave)

  15. Alexandre Grigoriev says:


    Try to define your "high priority" behavior. You’ll see that it doesn’t make sense.

    In one possible CS implementations, the threads would be waiting for the CS’s internal event, if the CS is already owned. When CS is released and the event is signalled, the kernel will release the first thread in the wait list (in most cases; barring some special circumstances). Thus, it gives CS some fairness. There is no facility to move a thread into the head of the wait list.

    Also, if at the same moment the CS is released, yet another thread tries to claim it, it could get it first, even though there are other threads in the line.

  16. WndSks says:

    @Alexandre Grigoriev: The point of a HighPriority function (aka AquireExclusiveStarveShared) is not to be fair. If you could set yourself as the ownerthread without actually having the lock, other waiting threads would see that the lock is held and after spinning, they would end up back at the end of the events wait list. (The tricky part is of course to not screw up the recursive aquire algorithm for the thread that really has the lock)

  17. Alexandre Grigoriev says:


    If you want a high priority requestor to put itself to the head of waiting queue, you need two events, meaning two waiting queues. It’s definitely doable. Write your own implementation, it’s not rocket science.

    If you mean exclusive/shared usage semantics (read-write locking), see InitializeSRWLock and related functions.

  18. mikeb says:


    You left off


  19. WndSks says:

    @Alexandre Grigoriev: InitializeSRWLock is Vista+ and is not recursive (and does not favor writers either since that is the point of my post)

    The problem with homegrown locks is the lack of debugger support. That is why I suggested that the missing CS api (if there is one) is not some kind of try-more, but a priority-enter

  20. @WndSks: That link you posted needs a HUGE cageat:

    Editor’s Update – 7/23/2004: This article is intended for informational purposes only and its contents should not be used to create production code. The article discusses undocumented data structures and processes internal to the Windows operating system that are subject to change in future versions, such as Longhorn.]

    Actually, Raymond even posted about that very article 6.4 years ago, though I can’t fault you for not knowing about that:


  21. AC says:

    I’m kind of surprised that someone asked this question. I’ve never hat to use critical sections myself and yet it seemed perfectly logical what "TryEnter.." means.

    Actually the title reads like it were from The Daily WTF.

  22. MarkKB says:


    Personally, I think those APIs should be named as follows:






  23. Steve says:

    Or perhaps,


Comments are closed.