The semaphore fits the producer-consumer model purely, although it has other uses. Your program logic is responsible for ensuring that the correct number of messages is made for the number of expectations. If you send a semaphore and no one is waiting for it, then when they wait, they will continue immediately. If your problem is such that it can be explained in terms of the meaning of the semaphore, then it is easy to solve it with a semaphore.
In some respects, the state variable is a little more forgiving. You can, for example, use cond_broadcast to wake up all the waiters, without the producer knowing how many there are. And if you are cond_signal condvar, no one is waiting on it, then nothing happens. Itβs good if you donβt know if the listener will be interested. In addition, the listener should always check the state with the mutex in front of the wait - if they do not, then they can skip the signal and not wake up until the next (which will never happen).
So, the condition variable is suitable for notifying interested parties that the state has changed: you acquire a mutex, change state, signal (or broadcast) condvar and release the mutex. If this describes your problem, you are in condvar territory. If different listeners are interested in different conditions, you can simply broadcast, and each of them, in turn, will wake up, find out if they found the condition that they want, and if you do not wait again.
It is very rude to actually try to use the mutex and semaphore in this way. The problem arises when you want to accept a mutex, check the status, and then wait for the semaphore for changes. If you cannot atomize a mutex and wait on a semaphore (which you cannot in pthreads), you end up expecting a semaphore by holding the mutex. This blocks the mutex, which means that others cannot accept it to make the changes you care about. Thus, you will be tempted to add another mutex in a way that depends on your specific requirements. And maybe another semaphore. The result is usually the wrong code with harmful race conditions.
Variable conditions come out of this problem because calling cond_wait automatically releases the mutex, freeing it up for others to use. The mutex is restored before cond_wait returns.
IIRC can implement some kind of condvar using only semaphores, but if the mutex that you implement to go with condvar requires a trylock, then this is a serious thug, and there are no timer expectations. Not recommended. Therefore, do not assume that everything you can do with condvar can be done with semaphores. Plus, of course, mutexes can have nice behavior that doesn't have semaphores, primarily avoiding priorities.