Why doesn't TryEnterCriticalSection try harder?

Raymond Chen

Raymond

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

Comments are closed.