Параллельное программирование на С++ в действии. Практика разработки многопоточных программ | страница 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). В противном случае нам больше никогда не удалось бы захватить мьютекс с более высоким уровнем иерархии, даже если поток не удерживает ни одного мьютекса. Поскольку мы сохраняем предыдущий уровень иерархии только в случае, когда удерживаем