GitHub: Part of my Multithreading tools: https://github.com/WindowsNT/mt
Introduction
You know how to use semaphore objects, which wait until someone starts accessing a protected object and up to a maximum number of threads.
However, very often you need the opposite: A way to know when all threads are finished with an object. This is a quick class that resolves this problem.
Note: I 'd love to implement it with standard C++ 11, but the standard lacks the very useful WaitForMultipleObjects() which is needed in our project.
Using the code
class reverse_semaphore
{
private:
HANDLE hE = 0;
HANDLE hM = 0;
volatile unsigned long long m = 0;
reverse_semaphore(const reverse_semaphore&) = delete;
reverse_remaphore& operator =(const reverse_semaphore&) = delete;
public:
reverse_semaphore()
{
m = 0;
hE = CreateEvent(0, TRUE, TRUE, 0);
hM = CreateMutex(0,0, 0);
}
~reverse_semaphore()
{
CloseHandle(hM);
hM = 0;
CloseHandle(hE);
hE = 0;
}
void lock()
{
WaitForSingleObject(hM, INFINITE);
m++;
ResetEvent(hE);
ReleaseMutex(hM);
}
void unlock()
{
WaitForSingleObject(hM, INFINITE);
if (m > 0)
m--;
if (m == 0)
SetEvent(hE);
ReleaseMutex(hM);
}
DWORD Wait(DWORD dw = INFINITE)
{
return WaitForSingleObject(hE, dw);
}
void WaitAndLock()
{
HANDLE h[2] = {hE,hM};
WaitForMultipleObjects(2,h,TRUE,INFINITE);
lock();
ReleaseMutex(hM);
}
HANDLE WaitAndBlock()
{
HANDLE h[2] = {hE,hM};
WaitForMultipleObjects(2,h,TRUE,INFINITE);
return hM;
}
};
Constructor/Destructor
Creates one mutex and one event for our work. Copying the class is not allowed. The destructor releases these objects.
lock()
Atomically increases usage counter by 1.
unlock()
Atomically decreases usage couter by 1. If this counter reaches 0, the event is set.
Wait()
Waits for the event. The event is set when all threads that have captured the object have released it.
WaitAndLock()
Waits for both the event and the mutex, ensuring that after all threads have finished with the object it is recaptured by the current thread. This is the function you will use most.
WaitAndBlock()
Waits for both the event and the mutex, ensuring that after all threads have finished with the object,no other thread can capture the object. The function returns the handle of the locked mutex, which should be released later with ReleaseMutex() after the calling thread finishes it's exclusive access to the object.
The use of WaitForMultipleObjects is required to avoid a race condition. Using this function ensures that the mutex is not owned until all threads have released the object. Without it, the function could be able to continue after the event was set (i.e. when all threads were finished) but, before owning the mutex, another thread might capture the object.
Let us test an example usage:
void TestRevSem()
{
reverse_semaphore u;
vector<thread> threads;
for (int i = 0; i < 10; i++)
{
threads.emplace_back(
[&](int slp)
{
if (true)
{
std::lock_guard<UWL::reverse_semaphore> lg(u);
Sleep((10 - slp) * 1000);
}
Sleep(5000);
},i
);
}
u.Wait();
cout << "All threads released the object, threads still running";
for (auto& t : threads)
t.join();
}
This sample code creates 10 threads that lock the reverse semaphore, then unlock it on a timer. After all threads have released it, a message is printed. The threads still run for 5 more seconds.
Good luck in using it!