Implement condition variables on RwLock

I want to wait a value that is protected by an RwLock until it satisfies a certain condition, after some searching, I have found the following implementation: https://github.com/Amanieu/parking_lot/issues/165#issuecomment-515991706, but the original comment doesn’t say anything about how to call notify_all. It suspect I have to lock the associated Mutex when calling notify_all, otherwise the program could hang. See this example (playground link):

use parking_lot::{Condvar, Mutex, RwLock, RwLockReadGuard};
use std::thread;

struct RwLockCondvar {
    condvar: Condvar,
    mutex: Mutex<()>,
}

impl RwLockCondvar {
    fn wait<T>(&self, read_guard: &mut RwLockReadGuard<'_, T>) {
        let guard = self.mutex.lock();

        RwLockReadGuard::unlocked(read_guard, || {
            let mut guard = guard;

            self.condvar.wait(&mut guard);
        });
    }

    fn notify_all(&self) {
        // It seems this locking is necessary.
        let _guard = self.mutex.lock();

        self.condvar.notify_all();
    }
}

fn main() {
    let flag = RwLock::new(false);

    let condvar = RwLockCondvar {
        condvar: Condvar::new(),
        mutex: Mutex::new(()),
    };

    thread::scope(|scope| {
        for i in 0..1000 {
            println!("Iteration = {i}");

            *flag.write() = false;

            // Wait `flag` to become `true`.
            let thread_1 = scope.spawn(|| {
                let mut guard = flag.read();

                while !*guard {
                    condvar.wait(&mut guard);
                }
            });

            // Set `flag` to `true` and notify.
            let thread_2 = scope.spawn(|| {
                *flag.write() = true;

                condvar.notify_all();
            });

            thread_1.join().unwrap();
            thread_2.join().unwrap();
        }
    });

    println!("Done");
}

Is this a correct implementation of a Condvar on RwLock?

explain parking lot used for

It is what the original issue uses. I guess the standard library could also work, but I don’t want to deal with poisoning.

It seems that I don’t even have to holding the lock when notifying, a dummy lock-and-release seem to be enough:

struct RwLockCondvar {
    condvar: Condvar,
    mutex: Mutex<()>,
}

impl RwLockCondvar {
    fn wait<T>(&self, read_guard: &mut RwLockReadGuard<'_, T>) {
        let guard = self.mutex.lock();

        RwLockReadGuard::unlocked(read_guard, || {
            let mut guard = guard;

            self.condvar.wait(&mut guard);
        });
    }

    fn notify_all(&self) {
        // Can we release lock immediately here?
        drop(self.mutex.lock());

        self.condvar.notify_all();
    }
}
1 Like