Glibc/musl already do this for you, but this may still be useful on other platforms!
This looks racy to me, can someone explain where I’m going wrong?
Thread A is the first to acquire the benephore, checks and increments the atomic variable, finds it is zero and proceeds without acquiring the semaphore.
While Thread A is in the critical section Thread B arrives and acquires the benephore, finds the atomic variable to be non-zero so acquires the semaphore. Because it has the semaphore it proceeds into the critical section. Now there are two threads concurrently in the critical section.
What prevents this scenario?
I think you’re right, unless I’m missing something obvious.
Worse still, if T1 enters the critical section and is followed by T2, if T1 now makes progress it will find benaphore_atom > 1 and call release_sem() on a semaphore it doesn’t hold. Which is probably either a crash or UB, who knows.
benaphore_atom > 1
I was missing something obvious.
The semaphore, is initialized into an ‘unavailable’ state.
When Thread B attempts to acquire the newly initialized semaphore, it blocks as the semaphore is in its ‘unavailable’ state.
Thread A later finishes up in its Critical Section, and seeing that benaphore_atom > 1 it increments the semaphore, allowing Thread B to make progress.
At the end of this execution, T2 sees !(benaphore_atom > 1) and continues without marking the semaphore as available.
!(benaphore_atom > 1)
Semaphores don’t provide mutual exclusion.
You use them to e.g. count the number of items in a queue and wake up enough threads to process them. Then those threads use a separate mutex to synchronise any operations on the queue.