What does the parking_lot documentation mean when it says 'allow raw (un)locking without a RAII guard object'?

Viewed 795

The docs for parking_lot say:

  1. Mutex and RwLock allow raw unlocking without a RAII guard object.
  2. Mutex<()> and RwLock<()> allow raw locking without a RAII guard object.

There is no further mention of these features, what they mean and how to use them. What are some pointers or sample uses?

2 Answers

The Mutex API controls access to its data via a guard, which unlocks the Mutex when it goes out of scope. The Mutex owns its data and can enforce that it is only accessible through a MutexGuard when it is locked. Both std::sync::Mutex and parking_lot::Mutex are the same in this regard.

However, parking_lot::Mutex also exposes its internals, which are a raw pointer to the data and a RawMutex. A RawMutex is just a lock, which does not control access to the data, but just tracks the state of the lock.

One reason to use RawMutex might be for times when it is very inconvenient to keep a MutexGuard in scope, and you are prepared to manage the lock status yourself. This is more likely in a library that defines new synchronization primitives or smart pointers rather than in application code, but you might also find it useful if you were mechanically translating existing C/C++ code to Rust.

By way of a simple example, these functions do the same thing as each other, but one uses the unsafe RawMutex:

use parking_lot::{Mutex, lock_api::RawMutex as _};

fn update_mutex(mutex: &Mutex<i32>) {
    let mut guard = mutex.lock();
    *guard = 2;
    // guard goes out of scope here, causing the Mutex to be unlocked
}

fn update_mutex_raw(mutex: &Mutex<i32>) {
    let raw_mutex = unsafe { mutex.raw() };
    let data = mutex.data_ptr();
    raw_mutex.lock();
    unsafe { 
        *data = 2;
        // need to manually unlock the RawMutex
        raw_mutex.unlock();  
    };
}

RawMutex.unlock() is unsafe because it would trigger Undefined Behaviour to call it when the murex is not locked. Dereferencing the data pointer twice at once would be Undefined Behaviour too, so it's up to you to make sure you don't, by honouring the lock state of the RawMutex.

As always, when using unsafe functions, read the documentation carefully and make sure you thoroughly undersand the invariants that you must preserve in order to avoid Undefined Behaviour.

Rust guarantees that safe code will not contain data races (concurrent mutable access to the same object). Mutexes allow for threads to have mutually exclusive access to read/write an object, thereby avoiding the race.

In other languages, (Java and C++ come to mind) mutexes aren't explicitly associated with data. It is up to programmers to make sure they lock and unlock them appropriately, accessing the data only within the critical section between the lock and unlock. In Rust, this would mean that safe code could contain data races if things were written incorrectly.

The solution to this is the RAII guard. The mutex "owns" the associated object and only allows read/write access through a RAII guard that represents a lock on the mutex. This is the MutexGuard type returned by std's Mutex::lock()

Parking_lot is claiming to allow locking/unlocking without creation of a RAII guard, which can be useful when writing unsafe code doing fancy things with mutexes for speed reasons. This differentiates it from std's sync::Mutex, which does not provide these methods.

The primary limitation of RAII guards is that by the nature of RAII they only last as long as the enclosing scope. Further, they hold a reference to the mutex, so "storing" the "lock" past its scope is hard due to Rust's borrowing rules.

The referenced parking_lot methods are the unsafe raw_unlock and the safe raw_lock. Since a raw_lock() needs an associated raw_unlock() to finish the critical section, use of these features means delving into unsafe code, which is usually ill-advised and unnecessary unless you have good reason to believe its the only way to accomplish what you need.

Related