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



вызывает >high_level_func(), которая захватывает мьютекс >high_level_mutex(4) (со значением уровня иерархии 10000 (1)), а затем вызывает >low_level_func()(5) (мьютекс в этот момент уже захвачен), чтобы получить параметр, необходимый функции >high_level_stuff(). Далее функция >low_level_func() захватывает мьютекс >low_level_mutex(3), и в этом нет ничего плохого, так как уровень иерархии для него равен 5000 (2), то есть меньше, чем для >high_level_mutex.

С другой стороны, функция >thread_b() некорректна. Первым делом она захватывает мьютекс >other_mutex(10), для которого уровень иерархии равен всего 100 (7). Это означает, что мьютекс призван защищать только данные очень низкого уровня. Следовательно, когда функция >other_stuff() вызывает >high_level_func()(8), она нарушает иерархию — >high_level_func() пытается захватить мьютекс >high_level_mutex, уровень иерархии которого (10000) намного больше текущего уровня иерархии 100. Поэтому >hierarchical_mutex сообщит об ошибке, возбудив исключение или аварийно завершив программу. Таким образом, взаимоблокировки между иерархическими мьютексами невозможны, так как они сами следят за порядком захвата. Это означает, что программа не может удерживать одновременно два мьютекса, находящихся на одном уровне иерархии, поэтому в схемах «передачи из рук в руки» требуется, чтобы каждый мьютекс в цепочке имел меньшее значение уровня иерархии, чем предыдущий, — на практике удовлетворить такому требованию не всегда возможно.

На этом примере демонстрируется еще один момент — использование шаблона >std::lock_guard<>, конкретизированного определенным пользователем типом мьютекса. Тип >hierarchical_mutex не определен в стандарте, но написать его несложно — простая реализация приведена в листинге 3.8. Хотя этот тип определен пользователем, его можно употреблять совместно с >std::lock_guard<>, потому что в нем имеются все три функции-члена, необходимые для удовлетворения требований концепции мьютекса: >lock(), >unlock() и >try_lock(). Мы еще не видели, как используется функция t>ry_lock(), но ничего хитрого в ней нет — если мьютекс захвачен другим потоком, то функция сразу возвращает >false, а не блокирует вызывающий поток в ожидании освобождения мьютекса. Она может вызываться также из функции >std::lock() для реализации алгоритма предотвращения взаимоблокировок.


Листинг 3.8. Простая реализация иерархического мьютекса

>class hierarchical_mutex {

> std::mutex internal_mutex;