Description
Condvar uses locks (mutexes).
Our condvar implementation could be changed to not use an internal mutex (condvar->m) and rather use some lock-free queue data structure, but this will gain little, because we are still left with the user mutex. I.e., to properly use a condvar one should use the idiom:
waiter:
mtx.lock();
if(!condition) { // or even "while" instead of "if"
cv.wait(mtx);
}
mtx.unlock();
waker:
mtx.lock();
condition=true;
mtx.unlock;
cv.wake_all();
Note how the waker needs to use a mutex, even if the wake_all() implementation doesn't internally, and if we have concurrent wakers some of them may go to sleep because of this "contention" - even if nobody is even waiting on the condition variable.
To design a truely lock-free alternative to convar, we also need to avoid the user mutex, so we can't use the traditional condvar API and need to use a slightly different one.
The reason that the user mutex was needed was to make the test of the condition, and the wait (in the waiter thread) one critical section - so that we don't lose a wakeup that happens after we tested the condition.
Instead of using a mutex for this purpose, we can do something similar to our wait_until() implementation, in some way rememebering that this thread is about to check the condition, so that a wakeup in this point will avoid a wait, even if the condition evaluated to false.
Basically, this will result in an API similar to our existing wait_until/wake(), just that wait_until() will register in a given object (call this perhaps a waitqueue?) and the target of a "wake" will not be a thread, but rather a waitqueue. For example:
waiter:
waitq.wait_until([&] {return condition});
waker:
condition = true;
waitq.wake_all();
Note how no mutex is needed when setting condition = true (it should be an atomic variable, though, and set with memory_order_relaxed) - wait_until will correctly handle the case where it tests condition, it is still false, but before it starts waiting both the condition=true and the wake_all() come.
P.S. For simplicity, we can implement a first version of this new API using an internal mutex (just like condvar now has, and so do Linux's wait queues) and not a lock-free data structure. It won't be entirely lock-free, but at least the user will not have to use an additional mutex!