Параллельное программирование на С++ в действии. Практика разработки многопоточных программ | страница 58



> unsigned long const hierarchy_value;

> unsigned 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() {

>  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)

Главное здесь — использование значения типа >thread_local для представления уровня иерархии в текущем потоке, >this_thread_hierarchy_value(1). Оно инициализируется максимально возможным значением (8), так что в начальный момент можно захватить любой мьютекс. Поскольку переменная имеет тип >thread_local, то в каждом потоке хранится отдельная ее копия, то есть состояние этой переменной в одном потоке не зависит от ее состояния в любом другом. Дополнительные сведения о >thread_local см. в разделе А.8 приложения А.

Итак, при первом захвате потоком объекта >hierarchical_mutex значение >this_thread_hierarchy_value в нем будет равно >ULONG_MAX. Это число по определению больше любого другого представимого в программе, потому проверка в функции >check_for_hierarchy_violation()(2) проходит. Раз так, то функция >lock() просто захватывает внутренний мьютекс (4). Успешно выполнив эту операцию, мы можем изменить значение уровня иерархии (5).

Если теперь попытаться захватить другой объект >hierarchical_mutex, не освободив первый, то в переменной >this_thread_hierarchy_value будет находиться уровень иерархии первого мьютекса. Чтобы проверка (2) завершилась успешно, уровень иерархии второго мьютекса должен быть меньше уровня уже удерживаемого.

Теперь мы должны сохранить предыдущее значение уровня иерархии в текущем потоке, чтобы его можно было восстановить в функции >unlock()(6). В противном случае нам больше никогда не удалось бы захватить мьютекс с более высоким уровнем иерархии, даже если поток не удерживает ни одного мьютекса. Поскольку мы сохраняем предыдущий уровень иерархии только в случае, когда удерживаем