A couple of months ago, while writing a multithreaded windows service i had the need to control how many requests of a certain type (and by type) were being served concurrently (requests were made with .Net remoting).
Ah a classic limited resource protection pattern i thought. The problem could easily be solved with a counting semaphore a synchronization primitive defined by the venerable Edsger W. Dijkstra.
Since .Net only provides a monitor implementation, I googled the colective in search of a .Net implementation.
I only found this one, the implementation even respects Dijkstra original dutch terminology
- Proberen te verlangen (wait) [P]
- verhogen (post) to increase a semaphore [V]
Which is why sometimes counting semaphores are called PV semaphores.
But i digress, this was not only confusing to me (considering my non existant dutch speaking skills), but it lacked a functions that i really needed. The ability to request a semaphore and wait for no more than a certain period (timeout and and the call would fail). So i improved the code a little and now i'm giving the code back to the collective. :-)
A semaphore semantic is quite simple. The semaphore has a counter associated.
- When a semaphore is released (P) the counter is incremented.
- When a semaphore is requested (V) if it's value is zero the caller is blocked until someone increments the semaphore (with a P call, sem_release in Posix syntax); if it's greater then zero the counter is decremented.
When we wish to protect N resources, all we have to do is initialize the semaphore with value of N (a mutex can be seen as a degenerated semaphore that has been initialized with the value of 1). Everytime a resource is being consumed we call sem_wait and when it's no longer needed you can (and should) signal it's availability with a call to sem_release.
Another use for a semaphore is to wait for a certain condition that will happen in the future and we wish to do nothing until that condition is met. This can be acomplished by initializing the semaphore with zero, then a thread (or threads) calls sem_wait (since the value is zero the call will block), when you wish to signal the thread (or one of the threads) that it can proceed it it's processing, you just have to call sem_release, the thread (or one of the threads) will awake and do it's thing.
With a few modifications this semaphore implementation can be transformed into a barrier but that is probably something for another post.
I don't know how the code performs under high load/high contention but it probably performs fine.
Disclamer: This code is provided as is. No warranties are made. It works for me it may work for you. I can only say, the code has been running for a few months without any problems.
I have decided to use wait and release instead of the Posix Syntax sem_wait and sem_release.
If you have any suggestions of improvments i would like to hear them. :-)
using System;
using System.Threading;
public class Semaphore {
private int _count;
public Semaphore() : this(1) { }
public Semaphore(int count) { _count = count; }
public bool Wait() {
lock(this) {
// Wait until a unit becomes available. We need to wait
// in a loop in case someone else wakes up before us. This could
// happen if the Monitor.Pulse statements were changed to Monitor.PulseAll
// statements in order to introduce some randomness into the order
// in which threads are woken.
while(_count <= 0)
if (Monitor.Wait(this) == false)
return false;
_count--;
return true;
}
}
private int GetMilliSecondsSince(DateTime since) {
TimeSpan t;
t = since - DateTime.Now;
return t.Seconds * 1000 + t.Minutes * 60000 + t.Hours * 3600000;
}
public bool Wait(int milliseconds) {
DateTime begin = DateTime.Now;
bool lockObtained = false;
try {
if ((lockObtained = Monitor.TryEnter(this,milliseconds)) == true) {
// Wait until a unit becomes available. We need to wait
// in a loop in case someone else wakes up before us. This could
// happen if the Monitor.Pulse statements were changed to Monitor.PulseAll
// statements in order to introduce some randomness into the order
// in which threads are woken.
while(_count <= 0) {
if (GetMilliSecondsSince(begin) > milliseconds)
return false;
if ((lockObtained= Monitor.Wait(this,milliseconds)) == false)
return false;
}
_count--;
return true;
} else {
return false;
}
} finally {
if (lockObtained) Monitor.Exit(this);
}
}
public void Release() {
// Lock so we can work in peace. This works because lock is actually
// built around Monitor.
lock(this) {
// Release our hold on the unit of control. Then tell everyone
// waiting on this object that there is a unit available.
_count++;
Monitor.Pulse(this);
}
}
}