Sergej Durmanov писал(а):
Кстати, в а2 есть рекурсивные блокировки
Честно говоря, не помню. Наоборот, как-то помнится, то ли у Мюллера, то ли у Реали, то ли у Фрая, встретилось утверждение, что их нет и, вообще, их использование считается ошибочным на уровне подходов к проектированию. Собственно, я и сам потом получил подтверждение этому в нескольких проектах (правда там всё на pthread реализовывалось или - над голым железом). Замечено было следующее: сначала кажется, что рекурсивный мьютекс решает некоторую проблему "объединения и универсализации" некоего набора функционала. Потом начинают расти количество "прилепок" в виде введения разного рода флагов "учёта состояния". Потом всё это начинает дополняться следующими слоями "заплаток на заплатках". И, естественным образом, происходит смешение уровней проектирования и представления системы и, в итоге, если ЭТО ВСЁ ещё остаётся работоспособным, то оно становится переусложнённой мешаниной.
Тут главное - в определённый момент остановиться и удалить разрешение рекурсивности. Потом, - просто перепродумать и перепроектировать набор методов, чётко разделив "пограничные"/интерфейсные методы, которые, собственно и реализуют монитор Хоара, и - внутренние реализации, вызываемые из нескольких интерфейсных методов. И всё становится стройным и аккуратным. И - главное - ПРАВИЛЬНО спроектированным. А это, поверьте, потом окупается сторицей, хотя в начале может и показаться, что решение получается чуть более "многословным". Но, опять-таки, поверьте, что это - только вначале. Потом оно начинает выигрывать по всем статьям у "заплаточно-мьютекснорекурсивного" решения.
Из недавних примеров я могу привести случай менеджера кучи.
Там есть три типовые интерфейсные классические операции: Alloc, ReAlloc и Free.
Естественно, что в каждой их них необходима работа монитора.
И, если для Alloc и Free всё происходит аккуратно, то для ReAlloc возникает "необходимость" сделать мьютекс в реализации монитора кучи рекурсивным. Почему? Потому, что логика работы ReAlloc, очень красиво выражается через вызовы Alloc и Free. Но они уже имеют внутри себя операцию залочивания мьютекса. Если мы оставим их вызовы внутри ReAlloc "как есть", без залочивания мьютекса внутри самой ReAlloc, то целостность кучи поломается. Поэтому залочивать требуется, потому, что сама логика выполнения операции ReAlloc должна быть "атомарной".
И, естественно, что программист с пониженной социальной ответственностью, ничтоже сумняшеся, просто объявит такой мьютекс, в мониторе кучи, рекурсивным.
Хотя, более правильным является следующее решение:
Мы оставляем в интерфейсных методах менеджера кучи (Alloc, ReAlloc и Free) залочивание и разлочивание мьютекса. А внутри залоченного участка вызываем внутренние реализации методов выделения и освобождения памяти (InnerAlloc и InnerFree).
Это - простой пример. Есть и более сложные, но они - больше по объёму.
В общем.
Простое правило: локирование (например, через мьютекс) внутренностей объекта должно быть ТОЛЬКО в интерфейсных методах. Если, в результате неудачного проектирования (с целью "повторного использования функциональности" внутри методов класса), вы сталкиваетесь с необходимостью сделать мьютекс рекурсивным, значит это - "звоночек" о том, что вы неправильно распределили функционал по интерфейсным и внутренним методам класса.
Заметьте, я постоянно говорю о мьютексах и активностях только в связке с объектами.
По сути, для меня сейчас уже НЕ существует отдельных понятий типа "задача", "поток", "мьютекс" вне привязки и преобразования их в ООП парадигме в "блоки эксклюзивного исполнения" и "активности" при проектировании системы.
Даже - на Си.
Просто уход от перечисленных понятий и внесение их в СВОЙСТВА объектов, а - не отдельных понятий в предметке, совершенно преображает работу и подходы к ней. Становится "чище" и "стройнее" и сама архитектура систем, и - её реализация в виде кода. Уходит масса "нюансов" и "мест, где надо что-то учесть", методы/функции становятся легковеснее в плане семантической нагрузки и чистоты-"одноуровневости", "нагруженности" работой с сущностями и операциями из других уровней представления системы.
Уходите от случая, когда вы мыслите понятиями "потоков", "пулами потоков", "задач", "заданий", "работ", выражаемых через системные потоки, которые "вызывают методы объектов" (которые, в свою очередь, по сути, остаются у вас только хранителями сгруппированных данных).
Вычищайте из описаний системы понятия "поток", "мьютекс", "кондвар"/"переменных условий".
Уводите вниз, их на системный уровень, и делайте их СВОЙСТВАМИ объектов ваших предметных областей.
КАК вы это сделаете - ваша забота и право на творчество.
Я обычно использую свою библиотеку (для Си), в которой у меня реализованы структуры/"классы" Monitor, Activity и - наборы макросов (за которыми прописаны входы и выходы в эксклюзивные секции и ожидание выполнения условия). Иногда это дополняется блокирующими Каналами и Таймерами (их реализация зависит от подлежащего железа и/или ОС). Хотя, в последнее время, я и таймеры для таймаутов делаю СВОЙСТВАМИ конкретных объектов (или мониторов), а не отдельными сущностями. Тайминг — вообще отдельный философский вопрос при проектировании.
Кстати, заметил интересную особенность. Надо будет её как-нибудь внимательней рассмотреть.
Так вот: в системах и в коллективах разработчиков, которые делают эти системы, которые тупо работают в старой парадигме потоков-мьютексов-кондваров, где последние рассматриваются как равноправные (а, иногда и ГЛАВНЫЕ) понятия проектируемых систем, очень часто возникает вопрос управления приоритетами потоков/задач/заданий.
А там, где система проектируется на основе активных объектов, такие "проблемы" возникают редко (у меня - вообще никогда не возникали).
Причём, когда на это указываешь, то оппоненты, начинают сильно волноваться и выдвигают в качестве аргументации случаи из своих систем с вопросом: "А КАК ТУТ ИНАЧЕ СДЕЛАТЬ, ЕСЛИ НЕ ВВОДИТЬ ПРИОРИТЕТЫ И НЕ ИЗМЕНЯТЬ ИХ?!"
ДА, действительно, в вашем случае и варианте проектирования системы - НИКАК ИНАЧЕ!
Просто потому, что выбранный вами подход и парадигмы проектирования к этому принуждают.
Второй этап дискуссии неизбежно приходит к фразе: "Ага, какой ты умный! Это ж что - всё перепроектировать?!"
Дальше в дискуссии традиционно возникает этап, когда человек может согласиться, что перепроектировать надо, но, при этом, "ничего не меняя". Ибо - «оно же работает!». То есть, человек может согласиться, и - даже проникнуться преимуществами подходов АО, но воспримет это - просто как "налепку ООП-ширм над структурами данных и системными ресурсами".
То есть, допускается традиционная ошибка: те же сами системные ресурсы, служащие для представления исполнимых сущностей и обеспечивающих эксклюзивность доступа к данным, просто оборачиваются в классовые интерфейсы. То есть, как сущности, с которыми оперируют проектировщики и программисты они - НИКУДА НЕ ДЕВАЮТСЯ.
А нам требуется их именно "ДЕТЬ".
УБРАВ (низкоуровневые, системные) механизмы реализации элементов многозадачности ИЗ рассмотрения их в предметной области.
"СМЕСТИВ" их из набора самостоятельных, выделенных сущностей проектируемой системы в "область" СВОЙСТВ объектов.
Но народ трудно в эту НЕОБХОДИМОСТЬ врубается.
И все опять, по-новой, продолжают "прыгать за бананом".