На этом примере демонстрируется еще один момент — использование шаблона std::lock_guard<>
, конкретизированного определенным пользователем типом мьютекса. Тип hierarchical_mutex
не определен в стандарте, но написать его несложно — простая реализация приведена в листинге 3.8. Хотя этот тип определен пользователем, его можно употреблять совместно с std::lock_guard<>
, потому что в нем имеются все три функции-члена, необходимые для удовлетворения требований концепции мьютекса: lock()
, unlock()
и try_lock()
. Мы еще не видели, как используется функция try_lock()
, но ничего хитрого в ней нет — если мьютекс захвачен другим потоком, то функция сразу возвращает false
, а не блокирует вызывающий поток в ожидании освобождения мьютекса. Она может вызываться также из функции std::lock()
для реализации алгоритма предотвращения взаимоблокировок.
Листинг 3.8. Простая реализация иерархического мьютекса
class hierarchical_mutex {
std::mutex internal_mutex;
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). В противном случае нам больше никогда не удалось бы захватить мьютекс с более высоким уровнем иерархии, даже если поток не удерживает ни одного мьютекса. Поскольку мы сохраняем предыдущий уровень иерархии только в случае, когда удерживаем internal_mutex
(3), и восстанавливаем его перед тем, как освободить этот внутренний мьютекс (6), то можем безопасно сохранить его в самом объекте hierarchical_mutex
, где его защищает захваченный внутренний мьютекс.