Counting semaphores can be used for resource protection, but they're typically used for different resources in different ways from a mutex semaphore.
A typical example would be a queue. For a dynamically sized queue, you have a counted semaphore to track how many items are currently in the queue. Consumers wait on that semaphore to tell them when they can read an item from the queue.
For a fixed-size queue, you add a second counted semaphore tracking the amount of empty space in the queue. Writers wait on it to determine when they're allowed to push an item to the queue.
You do often use a mutex semaphore in conjunction with those to assure that only a single thread modifies the queue itself at any given time.
For example, here's some code for a fixed-size queue using Win32 counted semaphores (and a mutex):
#ifndef QUEUE_H_INCLUDED
#define QUEUE_H_INCLUDED
#include <windows.h>
template<class T, unsigned max = 256>
class queue {
HANDLE space_avail; // at least one slot empty
HANDLE data_avail; // at least one slot full
CRITICAL_SECTION mutex; // protect buffer, in_pos, out_pos
T buffer[max];
long in_pos, out_pos;
public:
queue() : in_pos(0), out_pos(0) {
space_avail = CreateSemaphore(NULL, max, max, NULL);
data_avail = CreateSemaphore(NULL, 0, max, NULL);
InitializeCriticalSection(&mutex);
}
void push(T data) {
WaitForSingleObject(space_avail, INFINITE);
EnterCriticalSection(&mutex);
buffer[in_pos] = data;
in_pos = (in_pos + 1) % max;
LeaveCriticalSection(&mutex);
ReleaseSemaphore(data_avail, 1, NULL);
}
T pop() {
WaitForSingleObject(data_avail,INFINITE);
EnterCriticalSection(&mutex);
T retval = buffer[out_pos];
out_pos = (out_pos + 1) % max;
LeaveCriticalSection(&mutex);
ReleaseSemaphore(space_avail, 1, NULL);
return retval;
}
~queue() {
DeleteCriticalSection(&mutex);
CloseHandle(data_avail);
CloseHandle(space_avail);
}
};
#endif