Выбрать главу

На этом примере демонстрируется еще один момент — использование шаблона 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, где его защищает захваченный внутренний мьютекс.