A lock hierarchy is a rule for acquiring multiple locks in a consistent order. It prevents the circular wait in which one thread holds mutex A while waiting for B and another holds B while waiting for A. Use a fixed order when locks must be acquired separately; when they can be acquired together, `std::lock` is another way to avoid deadlock. section 3.2.5 section 3.2.4
The order must hold across every path that acquires the locks:
- A parameter-based rule can fail. If two threads swap the same two objects with their arguments reversed, each may lock a different object first. section 3.2.4
- Per-node locking makes the rule harder to maintain. Deleting a linked-list node requires locks on that node and both neighbors; traversals in opposite directions can acquire node locks in conflicting orders. section 3.2.5
- Calls made while holding a lock also matter. User-supplied code may acquire another lock, so its behavior must respect the ordering rule or be constrained. section 3.2.5
Numbered hierarchy levels
Assign a hierarchy value to each mutex and acquire higher-valued locks before lower-valued ones. With values 10000 and 5000, a function holding the 10000-level mutex can call one that locks the 5000-level mutex. A `hierarchical_mutex` can check the current thread’s level before locking and throw `std::logic_error` on a violation. section 3.2.5
Listing 3.8. A simple hierarchical mutex
class hierarchical_mutex
{
std::mutex internal_mutex;
unsigned long const hierarchy_value;
unsigned long previous_hierarchy_value;
static thread_local unsigned long this_thread_hierarchy_value; #1
void check_for_hierarchy_violation()
{
if(this_thread_hierarchy_value <= hierarchy_value) #2
{
throw std::logic_error("mutex hierarchy violated");
}
}
void update_hierarchy_value()
{
previous_hierarchy_value=this_thread_hierarchy_value; #3
this_thread_hierarchy_value=hierarchy_value;
}
public:
explicit hierarchical_mutex(unsigned long value):
hierarchy_value(value),
previous_hierarchy_value(0)
{}
void lock()
{
check_for_hierarchy_violation();
internal_mutex.lock(); #4
update_hierarchy_value(); #5
}
void unlock()
{
if(this_thread_hierarchy_value!=hierarchy_value)
throw std::logic_error("mutex hierarchy violated"); #9
this_thread_hierarchy_value=previous_hierarchy_value; #6
internal_mutex.unlock();
}
bool try_lock()
{
check_for_hierarchy_violation();
if(!internal_mutex.try_lock()) #7
return false;
update_hierarchy_value();
return true;
}
};
thread_local unsigned long
hierarchical_mutex::this_thread_hierarchy_value(ULONG_MAX); #8
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":5,\"ch\":69},{\"line\":5,\"ch\":71}],[{\"line\":8,\"ch\":69},{\"line\":8,\"ch\":71}],[{\"line\":15,\"ch\":69},{\"line\":15,\"ch\":71}],[{\"line\":26,\"ch\":69},{\"line\":26,\"ch\":71}],[{\"line\":27,\"ch\":69},{\"line\":27,\"ch\":71}],[{\"line\":32,\"ch\":69},{\"line\":32,\"ch\":71}],[{\"line\":33,\"ch\":69},{\"line\":33,\"ch\":71}],[{\"line\":39,\"ch\":69},{\"line\":39,\"ch\":71}],[{\"line\":46,\"ch\":69},{\"line\":46,\"ch\":71}]]"}
!@%STYLE%@!
The hierarchical mutex listing shows the per-thread hierarchy value, the pre-lock check, and the update made after a permitted acquisition. section 3.2.5
