Why doesn't TryEnterCriticalSection try harder?

Raymond Chen

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));
  WaitForCriticalSectionOwnerToLeave(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.)

0 comments

Discussion is closed.

Feedback usabilla icon