A pattern I've definitely both seen and used is
let guard1 = datastructure_containing_the_whole_world.lock();
let guard2 = guard1.subset_of_that_datastructure.lock();
guard1.unlock();
// Do expensive work
guard2.unlock();
Which works to parallelize work so long as guard2 isn't contended... and at least ensures correctness and forward progress the rest of the time.There’s no priority inversion possible because locks can only ever be held in decreasing orders of priority - you can’t acquire a low priority lock and then a high priority lock since your remaining MutexKey won’t have the right level.
Mutex::new(AppConfig::default());
...is meant to be acquiring a mutex protecting some global config object, yes? That's what I'm calling a "global lock".> There’s no priority inversion possible because locks can only ever be held in decreasing orders of priority
T1 T2
-- --
small_lock();
big_lock();
small_lock(); <--- Spins waiting for T1
...and now any other thread that needs big_lock() spins waiting for T2 to release it, but T2 is spinning waiting for T1 to release the (presumably less critical) small lock.If small_lock is never ever acquired without acquiring big_lock first, small_lock serves no purpose and should be deleted from the program.
Look at the API - if big_lock and small_lock are at the same level, you would need to acquire the lock simultaneously for both locks which is accomplished within the library by sorting* the locks and then acquiring. If you fail to acquire small_lock, big lock isn’t held (it’s an all or nothing situation). This exact scenario is explained in the link by the way. You can’t bypass the “acquire simultaneously” api because you only have a key for one level
Your terminology is also off. A lock around a configuration is typically called a fine grained lock unless you’re holding that lock for large swathes of program. Global as it refers to locking doesn’t refer to visibility of the lock or that it does mutual exclusion. For example, a lock on a database that only allows one thread into a hot path operation at a time is a global lock.
* sorting is done based on global construction order grabbed at construction - there’s a singleton atomic that hands out IDs for each mutex.
Mutex::new(AppConfig::default()) might very well be a small, leaf mutex.