m-ou-se / rust-atomics-and-locks

Code examples, data structures, and links from my book, Rust Atomics and Locks.
Other
1.33k stars 120 forks source link

Question about drop on the condition variable example of Chapter 1 #34

Open francoisthire opened 1 year ago

francoisthire commented 1 year ago

The content that the question is about

In the section of Chapter 1 related to condition variables (https://marabos.nl/atomics/basics.html#condvar), we can found the following example:

use std::sync::Condvar;

let queue = Mutex::new(VecDeque::new());
let not_empty = Condvar::new();

thread::scope(|s| {
    s.spawn(|| {
        loop {
            let mut q = queue.lock().unwrap();
            let item = loop {
                if let Some(item) = q.pop_front() {
                    break item;
                } else {
                    q = not_empty.wait(q).unwrap();
                }
            };
            drop(q);
            dbg!(item);
        }
    });

    for i in 0.. {
        queue.lock().unwrap().push_back(i);
        not_empty.notify_one();
        thread::sleep(Duration::from_secs(1));
    }
});

The question

This example is adapted from the one using park and unpark mentioned previously. However, in this example, we can see the line:

drop(q)

I guess this drop is about releasing the lock sooner, but it seems optional. Is there any particular reason to introduce it?

mingmamma commented 2 months ago

I'd add some thoughts to this and hope someone can add more feedback. To the original poster's question, the short answer is that the explicit statement drop(q), where q is the mutex lock guard(or a value of MutexGuard<'_, VecDeque<_>>, to be precise in this example), is indeed to release the mutex lock sooner, as opposed to the variable being dropped at the end of its scope. The short argument for this explicit drop is necessary is that this is a performance consideration. This pattern of can be viewed as some sort of basic implementation of a message queue, if we see the spawned thread that goes in the loop, and pop item from the front of the VecDeque as the receiving side, and the main thread that pushes one item into the back of the queue every 1 second as the sending side (c.f. we can see in the first half of this Rust educational video about channels that this pattern is used to give a basic implementation of a mpsc channel: youtu.be/b4mS5UPHh20). Within the video, the instructor gave a good explanation of the benefit of such explicit drop: noting that in such set up, both the receiver and sender needs to lock mutex to access the shared queue, and we wish to keep the contention of the lock as minimal as possible by keeping the critical section short (since we are dealing with fairly low-level constructs). In this example, practically that means to explicitly call drop before using the received item since the lock is not needed to use the received item. It seems not much different in this example since the use of the item is just dbg!() but that's a good demonstration of the consideration. Also noting that for the reason discussed, the implementation of such pattern involving lock is not high performant, but I haven't studied the real-world solutions that makes it more performant and how that would work. 😓 Hope someone can give better input to this issue.

Addition: Just wanted to add the book itself comments on this issue in a later chapter, which I read before and probably have influenced my view above (https://marabos.nl/atomics/building-channels.html#a-simple-mutex-based-channel)