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).
A charitable reading of the parent comment might be that Rust already implements these particular low-level primitives, so that by using Rust, you are not using the problematic glibc pthread code. See https://blog.rust-lang.org/2022/06/30/Rust-1.62.0.html#thinn... for the relevant announcement: "Previously, Mutex, Condvar, and RwLock were backed by the pthreads library on Linux. [...] Rust's standard library now ships with a raw futex-based implementation of these locks on Linux, [...]"
All Rust wanted was a futex, which for reference is just any 32-bit aligned value. The pthread_mutex_t is less useful to Rust, yet it's ten times bigger!
So, Mara's new Mutex<T> in the Linux builds of Rust is just a futex (32-bits) plus a poison boolean plus your type T which is protected by the mutex. If your type is Zero Size then this rounds up to 8 bytes, five times smaller than pthread_mutex_t. If your type was just a u16 (unsigned 16-bit integer) then it rounds up to the same size, but you're also protecting your 16-bit unsigned integer which is nice.
Now, if you're protecting huge data structures with mutexes, you don't care, it makes no measurable difference. But if you heard futex is small (it is) and protected loads of tiny objects with one each (say, individual 32-bit floating point X,Y co-ordinate pairs) Rust now makes your choice cost roughly what you expected instead of bloating your program for no reason.
It's also a bit faster (but probably not enough that you should care) and simplifies some other book keeping in Rust itself.
[ Above I wrote about Mutex, for Condvar the argument is similar but I haven't spent lots of time understanding the innards of Condvar whereas I spent a week reading Mutex stuff ]
I see the benefit of Rust here as being a kindof modern refresh of the building block concepts, which have calcified in standard C. With Rust, the refresh is now in std, or in a popular crate (where it is accessible via package managers). Alternative C standard libraries are hampered by having to implement awful parts (like locales, the string routines), and while C has an even greater quantity of excellent alternatives for smaller slices of "std" functionality (both open and closed source/in-house), discovery is less good. I'm not sold on every aspect of package managers, but I can't deny they have made some difficult things easy.
However from a quick glance Rust's Condvar is much smaller than pthread_cond_t
Condition variables per se don't seem that difficult. Mara did the Linux and Windows condition variables, both are pretty elegant. As with a graceful swan, there is lots happening out of sight (in the futex code Rust needs to cope with the fact that when people do futexes for $notLinux including OpenBSD and Fuchsia they inevitably put a spin on the idea, adding something they thought was cool or ignoring a hard problem they didn't fancy solving) but the general idea is very simple.
In contrast the glibc implementation is fiendishly complicated, but that's not because of POSIX, it's because they decided to do something fiendishly complicated, it hardly seems fair to demand that everybody who didn't need that complexity should respect how difficult it was anyway.
Example, glibc promises if six threads A, B, C, D, E, F wait on a condition variable in order and then it is signalled exactly four times, threads A, B, C and D are definitely woken and E, and F are definitely not woken. That's potentially useful for some purposes, but it's expensive, what if I don't care about this? Too bad, the price is baked in even though POSIX says nothing about this feature.
Rust doesn't care, so, if A, B, C, D, E, F wait on the CondVar and then it gets signalled exactly four times, well, four threads wake, but it could be any four. If A always waits again, signalling four times could just wake A four times, and each time it goes back to sleep. That seems useless, but if you wrote A, that's on you.
I'm sure the rust devs got their condvar implementation exactly right at the first try, including cancellation, RT support, support for robust mutexes, wait morphing, process shared cond vars, etc.
For human programmers like the glibc ones, it took many decades, and there are very few that are confident enough to make large changes.
As I understand, there was no posix correct cond var for windows for many years untill after vista MS decided to expose the right primitives.
Where do you see this guarantee ?
> including cancellation, RT support, support for robust mutexes, wait morphing, process shared cond vars, etc
I'm sure doing the 100 metres sprint backwards, hopping on one leg while balancing a bowl of soup on your head would be trickier than the way Usain Bolt did it, but so what ?
What glibc tries to do is very complicated, yet when we see people using this feature they overwhelmingly don't require any of the complications, they just want a condition variable and that's all Rust's Condvar offers.
The posix spec is not the easiest to read, but pthread_cond_signal has wordings on the effect that the ordering of wakeups is unspecified except under realtime scheduling.
In any case glibc was blasted in the posix bug tracker for not respecting the wake up order even on non-RT and asking for the spec to be amended. Instead glibc had to rewrite its implementation to conform with the spec (and that fix introduced this bug).
> I'm sure doing the 100 metres sprint backwards, hopping on one leg while balancing a bowl of soup on your head would be trickier than the way Usain Bolt did it, but so what ?
you said that the complexity in glibc is not due to POSIX. All the items I mentioned (except for wait morphing) are required by POSIX/SUS. So what is it then?