Whenever I go to utilise SemaphoreSlim, I stare at the constructor parameters in bewilderment. What exactly is the difference between initial count and max count, do I need both and why? By way of analogy lets break it down.
Analogy: The Bouncer
Imagine owning a nightclub that can safely contain a number of people at any one time. To control this, you’ve enlisted a bouncer that hands out tickets. If the bouncer has a ticket, it’s handed to the next person in the queue and they’re allowed in. If the bouncer has no tickets, the queue waits until more are available.
The bouncer is given 30 tickets and the doors open.
|
|
30 tickets are handed out immediately, but there’s a problem, only 30 people ever enter. After the 30th ticket is handed out, the bouncer is provided no additional tickets, and the queue waits indefinitely, even when all patrons have left.
Let’s have it so when a patron leaves, they hand their ticket back.
|
|
What if a person injures themselves and is carried out via ambulance and thus their ticket is never given back. Fewer people can enter the club until the bouncer has no tickets to be handed out, and the queue waits indefinitely. In this stretched analogy this equates to an exception within the task (the while loop would also exit but let’s ignore that for now).
We should make it such that the ticket is always handed back when the patron leaves.
|
|
This provides a constant stream of 30 tickets, there’s no way in this simple setup for the bouncer to hold more than 30 at any time.
Let’s say we want to increase capacity as the night progresses. Start with 5 available tickets (not a very lively nightclub mind) and an additional 5 people are allowed to enter every 5 minutes.
|
|
At the start we have capacity for 5 people, half an hour later 10, then 15 and so forth. A problem emerges that we might allocate an infinite number of tickets, well beyond the safe capacity for our club!
This is where the second parameter comes in, this is the max count for how many tickets can exist at once. It provides a fail safe to over-allocating, preventing too many people entering at once.
|
|
A SemaphoreFullException
will be thrown if more than the max count is exceeded. The system should ideally be designed to allocate based on the max and the number requested rather than just silently falling over.
For basic concurrency throttling, it’s common practice to set both values to the number of threads you want to run concurrently. This prevents accidentally spawning a higher number of concurrent threads than a pre-determined safe limit.
Using the max limit parameter allows for implementing more complicated concurrency strategies like fixed or sliding time windows, safe in the knowledge you will never over allocate.