Creating a semaphore with a maximum count from WaitOnAddress


Last time, we created a simple semaphore from Wait­On­Address. That sempahore did not have a maximum token count. Let's add that.

struct ALT_MAXSEMAPHORE
{
  LONG TokenCount;
  LONG MaxTokenCount;
};

void InitializeMaxAltSemaphore(ALT_MAXSEMAPHORE* Semaphore,
                               LONG InitialCount,
                               LONG MaxTokenCount)
{
  Semaphore->TokenCount = InitialCount;
  Semaphore->MaxTokenCount = MaxTokenCount;
}

bool ReleaseMaxAltSemaphore(ALT_MAXSEMAPHORE* Semaphore,
                            LONG ReleaseCount)
{
  while (true) {
    LONG OriginalTokenCount = Semaphore->TokenCount;
    LONG NewTokenCount = OriginalTokenCount + ReleaseCount;
    if (NewTokenCount > Semaphore->MaxTokenCount) {
     return false; // would exceed maximum
    }
    if (InterlockedCompareExchange(&Sempahore->TokenCount,
      NewTokenCount,
      OriginalTokenCount) == OriginalTokenCount) {
      if (ReleaseCount == 1) {
        WakeByAddressSingle(&Sempahore->TokenCount);
      } else {
        WakeByAddressAll(&Sempahore->TokenCount);
      }
      return true;
    }
  }
}

Releasing the tokens is a little trickier because we have to verify that we aren't going to exceed the maximum token count. Our attempt to release may fail if other threads snuck in and either claimed or released tokens, in which case we loop back and try again.

The code to wait for and claim a token is unchanged. We merely had to tweak how we release tokens.

Okay, next time, we'll specialize to the case of an event, which is like a semaphore with a maximum count of 1.

Comments (8)

  1. Pierre B. says:

    One wonders what happens when the release fails due to the exceeded maximum count. I guess that’s one case where calling abort() makes some sense. Or putting whatever was managed by the semaphore in an invalid state and stop all requests. After all, it looks like counts would be leaked. (It’s hard to believe that the caller that provided the wrong count (thus proving that’s it’s buggy) can fix things up… Not that somehow stopping processing things associated with the semaphore won’t have bad consequences for the rest of the system.

    Just an instance of garbage in, garbage out. Once you start with bad data, everything’s up.

    1. Joshua says:

      I can’t imagine any use of a maximum count either. When I went to school they taught it as resource consumption but the modern semaphores don’t consume more with larger counts.

      1. Harry Johnston says:

        Perhaps if the semaphore is limiting use of a resource, e..g, “only let three working threads at a time perform resource-hungry task A” ? Typically, you would both claim and release the semaphore in the working threads, so the maximum count would only come into play if there was a bug, but I guess there might be scenarios where it is more convenient to release the semaphore from other threads and rely on the maximum count to avoid over-committing. (A producer-consumer queue of some sort?)

  2. Karellen says:

    Why would you want a semaphore with a maximum token count? What’s the use case?

    The only thing I can think of is as a debugging aid. You know if you hit the maximum token count that one of the users is buggy and is releasing a semaphore too many times, either releasing a held semaphore more than once, or releasing a semaphore they never actually held. Spotting this and crashing before any more data gets corrupted, or breaking into a debugger, is your best bet.

    However, in my experience with synchronisation primitives, the place where you notice the bug is rarely the place where the bug occurred. If the bug is at site A, then site A typically puts the primitive into a valid but unexpected state, and its only when site B tries to manipulate the primitive some time later that you notice it’s not in the state you thought it was in. So your data is already corrupt, and the debugger is showing you a call site where the code is correct.

    I’ve had better results using static analysis, virtualisation/runtime monitoring, or adding copious logging (printf-style debugging) to track those down.

    1. Harry Johnston says:

      Just knowing that there’s a bug can be useful, I guess, even if it doesn’t give you much of a clue as to the cause.

  3. DWalker07 says:

    It’s odd that RELEASING a token is where the maximum token count comes in to play. It sounds counterintuitive. Releasing a token should decrease the token count!

    1. Ben Voigt (Visual Studio and Development Technologies MVP with C++ focus) says:

      Releasing a token transfers ownership from the thread back to the semaphore itself, so the semaphore’s count of available tokens goes up.

      1. DWalker07 says:

        You are right, of course. Thanks.

Skip to main content