We discuss more advanced part of the Rust programming language: the concurrency. Already covered contents will not be discussed in this note.

Lock Poisoning

Mutex in Rust gets marked as poisoned when a thread panics while holding the lock because the panicked thread will hold the lock forever. When that happens, the Mutex will no longer be locked, but calling its lock method will result in an Err to indicate it has been poisoned. However, Err returned from poisoned mutexes are often disregarded and the calling process will simply panic to prevent errors from propagating to more processes.

MutexGuard's Lifetime

A well-designed Mutex will typically implement the Drop trait as follows.

impl<'a, S, T> Drop for MutexGuard<'a, S, T>
	where S: LockStrategy,
{
		fn drop(&mut self) {
				self.mutex.unlock();
		}
}

So it means we can always assume that the lock will be dropped until if ends it lifetime, but sometimes, we may encounter some subtle problems due to lifetime mess-ups. For example,

vec.lock().push(123); // Ends after this statement. OK.

{
		let _ = vec.lock(); // Will not deadlock and dropped at the end of this scope.
}

if let Some(item) = vec.lock().front() {
		// A `let` statement will extend the lifetime until the while statement ends.
		vec.lock().remove(0); // Deadlock.
}

Because the above syntax sugar will be expanded to the following.

match vec.lock().front() {
		Some(item) => vec.lock().remove(0),
		_ => (),
}

So the lifetime ends after match is done. Therefore, a good coding habit for handling locks would be to always acquire a lock explicitly and drops it when necessary or wrapping the lock in a scope.

Parking Lot and Conditional Variables

When data is mutated by multiple threads, there are many situations where they would need to wait for some event, for some condition about the data to become true. This is usually called conditional variable or CondVar. There is a wonderful crate called parking_lot that provides this functionality. While a mutex does allow threads to wait until it becomes unlocked, it does not provide functionality for waiting for any other conditions.

A thread can park itself if the condition is not satified and sleeps until other threads that work with the conditional variable notify the parked thread and wakes it up. The below examples illustrate the philosophy (although over-simplified):

use std::collections::VecDeque;
use std::time::Duration;
use std::sync::Mutex;
use std::thread;

fn main() {
    let queue = Mutex::new(VecDeque::new());

    thread::scope(|s| {
        // Consuming thread
        let t = s.spawn(|| loop {
            let item = queue.lock().unwrap().pop_front();
            if let Some(item) = item {
                dbg!(item);
            } else {
                thread::park();
            }
        });

        // Producing thread
        for i in 0.. {
            queue.lock().unwrap().push_back(i);
            t.thread().unpark();
            thread::sleep(Duration::from_secs(1));
        }
    });
}

This is ineffective because

However, these problems can be easily tackled with by adding waiting queue (CondVar) and a Mutex lock.