What if you just want to limit how many things you can have or can be done? This episode will explain another side of the semaphore sometimes called a counting semaphore.
When you’re using semaphores, you have a way to wait on the availability of something, but you also really need to have some way to inform the other side when there’s more room to add something. If you really don’t have a need for some other code to run during this, then why have a whole other side in the first place? In this case, all you really need to do is have your thread signal the same semaphore that it waited on when it’s done so that some other thread can have a turn.
Just be aware that this isn’t the original intention of semaphores and that you might be overlooking an opportunity to add some extra flexibility to your code by going with a simpler design. And you might even find that what at first seemed like the simpler design just led to more complexities as you try to maintain the “simple” design.
If you want to use semaphores like this, then the method names up and down make more sense to me than signal and wait. Listen to the full episode or you can read the full transcript below.
In the last episode, I explained how you can use two semaphores in a producer/consumer situation. You can actually use this anytime you want to send and receive signals from one thread to another. And in this situation, each thread should pick a side and stick with that side of the semaphore.
But there are other situations where you’re not really interested in sending signals or in producing and consuming anything. You just want to make sure that there’s a limit of some kind.
What do I mean by a limit? We have limits all the time in real life. Not the speed limit type. That’s not the kind of limit I’m talking about. Think of a restaurant that has a maximum capacity limit. For example, only 50 people are allowed inside the building at any given time.
In cases like this where you’re not interested in communicating availability of a newly produced item or consumption of something, then the advice about picking a semaphore side and sticking with it becomes clunky. When you enter the restaurant, you’re not really consuming anything. Hopefully, you’ll consume the food but that’s a different story. All you’re really doing is temporarily taking possession of one of the spaces available.
And because the possession is temporary, you really should make sure to return it when you’re done. If you don’t, then this would be like leaving the restaurant and taking the chair with you. This means that each thread representing a guest should call wait before entering the restaurant and then call signal when leaving.
After reviewing the previous episode, I also try to think of questions that you might have. Is there anything that I could have explained better? And I think I should take the time to explain more about how semaphores work before giving you yet another set of guidelines for how to use them.
I mentioned that semaphores have a capacity and a pair of methods such as signal and wait. But what does this mean? Let’s start with a small diner instead of a large restaurant. This diner can only hold 10 guests. What you can do is create a semaphore with a capacity of 10 to match the number of guests. This is the limit.
Semaphores work a little backwards that what you might suspect though. Instead of starting out at zero and going up to the limit minus one. Or in this case going from zero to nine. A semaphore instead starts out at the limit.
When a guest arrives, the guest should wait on the semaphore. You can think of waiting on a semaphore as similar to locking a mutex. If the semaphore is available, then there’s no wait and the guest can proceed. Just like if the mutex is not currently locked by another thread.
When the first guest successfully waits on the semaphore, the count of the semaphore will be reduced to 9. When a second guest waits on the semaphore, then the count gets reduced again to 8 and the second guest is allowed to proceed. This continues until the count reaches zero. You can think of this as if the semaphore is empty. And like a car with an empty gas tank isn’t going anywhere, also the next guest to wait on the semaphore will block. The wait call will not return back to the caller until there’s room.
And whenever some code signals a semaphore, then the count goes back up. This is why some semaphore designs use the names up and down instead of signal and wait. The signal method matches the up method because both names mean that the count goes up. And the wait method matches the down method. A lot of it has to do with the intended purpose of your semaphore class and the level of understanding of the developer who wrote it. A semaphore that uses up and down, to me anyway, is advertising the intended usage of the semaphore to be used where a single thread can both call up and down as needed.
I’ll continue comparing signal and wait with up and down right after this message from our sponsor.
( Message from Sponsor )
If we follow the very good advice from the last episode about producers and consumers, then whenever a guest leaves the diner, then the guest would have to signal a different semaphore. And some other code would presumably be waiting on that other semaphore and could then signal the diner entry semaphore that a space is now available.
If you have a need for some other code to be involved at this point, then this is actually a good design. It’s becoming more like a producer/consumer problem again. Maybe you need to clean the table first before the next guest gets mad at a dirty table and walks out vowing never to return again. If so, then you really do need to produce a new table. And the guests really do consume clean tables and leave behind dirty tables. In this case, the advice from the previous episode is exactly what you want.
But if not and if the leaving guest is perfectly able to let another guest enter, then why bother with another semaphore? If that other semaphore is there just to cause some code to signal the entry semaphore anyway, then you’re better of keeping your code simpler and signal the entry semaphore directly. This is what I’m talking about where your situation might not exactly fall under the normal producer/consumer problem.
If so, then I think it’s fine for the departing guest to go ahead and signal the same semaphore that was waited on in order to enter in the first place. This brings the model back to what you would expect with a mutex. Only where a mutex allows exclusive ownership to a single thread at a time, a semaphore used like this can grant access to multiple threads at the same time.
I mentioned this in the last episode but it’s worth repeating. Using a semaphore is not enough to protect actual resources. In the diner example, successfully waiting on the entry semaphore is just the ticket to get into the diner. This applies regardless of whether or not this is the only semaphore. Getting a specific table is a different story. All the semaphore says is that there should be a clean table somewhere in the diner.
At some point, you’re going to have to guarantee exclusive access to a particular table. After all, you don’t really want some stranger to sit down at your table uninvited, right? You’ll probably want a mutex to control access to a map that defines which table a guest should be using. Listen to episode 44 for a description of a dictionary which can help here.