Оба ваших примера - исходный synchronized_counter
и стек в вашем редактировании - являются правильными примерами использования рекурсивного мьютекса, но они будут считаться плохим дизайном API, если вы строите структуру данных. Я попытаюсь объяснить, почему.
Предоставление внутренних данных - вызывающая сторона должна использовать такую же блокировку, которая защищает внутренний доступ к членам структуры данных. Это открывает возможность неправомерного использования этой блокировки в целях, отличных от доступа к структуре данных. Это может привести к конфликту блокировок или, что еще хуже, к тупику.
Эффективность - часто эффективнее выполнять специализированные массовые операции, такие как increment_by(n)
или pop_many(n)
,
- Во-первых, это позволяет структуре данных оптимизировать операции - возможно, счетчик может просто выполнить
count += n
или стек может удалить n
элементов из связанного списка за одну операцию. [1] - Во-вторых, вы экономите время, не блокируя и не разблокируя мьютекс для каждой операции. [2]
Возможно,лучший пример использования рекурсивного мьютекса был бы следующим:
- У меня есть класс с двумя методами
Foo
и Bar
. - Класс был разработан, чтобы быть одно-
- Иногда
Foo
вызывает Bar
.
Я хочу сделать класс потокобезопасным, поэтому я добавляю мьютекс в класс и блокирую его внутри Foo
и Bar
. Теперь мне нужно убедиться, что Bar
может заблокировать мьютекс при вызове из Foo
.
Один из способов решить эту проблему без рекурсивного мьютекса - создать приватный unsynchronized_bar
и иметь оба Foo
и Bar
вызовите его после блокировки мьютекса.
Это может быть сложно, если Foo
является виртуальным методом, который может быть реализован подклассом и используется для вызова Bar
, или если Foo
вызывает какую-то другую часть программы, которая может перезвонить в Bar
. Однако, если у вас есть код внутри критических секций (код, защищенный мьютексом), вызывающий другой произвольный код, поведение программы будет трудным для понимания, и легко вызвать взаимные блокировки между различными потоками, даже если вы используетерекурсивный мьютекс.
Лучший совет - решать проблемы параллелизма с помощью хорошего дизайна, а не причудливых примитивов синхронизации.
[1] Существуют некоторые шаблоны трикера, такие как «выскочить элемент, посмотритепри этом решите, если я вставлю еще один ", но это может быть реализовано путем предоставления предиката для массовой операции.
[2] Практически говоря, блокировка мьютекса, который у вас уже есть, должна быть довольно дешевой, но в вашемНапример, для этого требуется как минимум вызов внешней библиотечной функции, которая не может быть встроена.