Semaphores alone can’t mimic the same semantics (juggling between multiple, disparate locks/sync objects) without running into a possible race condition unless you have an API that can somehow wait on one of two or more semaphores to become available.
A Win32 mutex or semaphore is very heavy and typically avoided in a sane high performance application unless you need it to cross process boundaries. A lot of the time (most?) you will want a win32 critical section, which is similar to the modern futex based pthread mutex implementation in that it does not enter the kernel unless there is contention.
A pthread condition variable is clumsy. You need to give it an extra mutex and loop etc. It doesn't have any of the simplicity of WaitForMultipleObjects, even with all the faults of that interface of which there are many.
My understanding is that condition variables were designed to be a superior alternative to Windows Event objects.
I'm glad to read here that others find them fiddly and overly awkward to use since that was also my impression vs the consistent and usually easy to use Windows thread synchronization HANDLE based objects. Dave Cutler definitely did a good job there IMHO.
In particular pthreads borrowed them from Mesa, but they originally were part of the monitor abstraction designed by Hoare.
So they weren't designed to be a superior alternative to windows events; they might be considered an alternative to events in general but I couldn't easily find any history of the events abstraction.
Events and signals aren’t intended to be used off you’re trying to share ownership of memory or data - you should use the std types in that case. They’re for signaling between threads or for building your own synchronization objects atop of. For example, an auto reset event can be used in lieu of a one-bit channel (sort of like Mpmc<()>) and manual reset events can be used to implement the same in broadcast mode, much more efficiently than the channel crates. A common usage is to block until an event is available or a shutdown has been requested - in lieu of manually polling an AtomicBool and aborting if it is true, making your code more responsive (no delay between polls) and more efficient (no repeated polling, no busy waits, etc). They can be used to signal state transitions or readiness, which doesn’t have anything to “own”, for example, the rsevents-extra crate [0] implements a countdown event (event becomes available when n outstanding tasks have finished in parallel threads) or a semaphore (restricting concurrency to a region of code, not limiting access to a variable or object).