Как стать автором
Обновить

Комментарии 15

Ничего нового.
Deadlock-и являются одной из самых неприятных проблем программирования, т.к. их появление зависит от динамики. В данной статье мы привели пример далеко не самого тривиального дедлока и, как показывает обсуждение ниже, далеко не все даже поняли, где тут может возникнуть ситуация взаимной блокировки. Мы решили поделиться опытом, чтобы другие не наступали на грабли. Если Вам это кажется тривиальным, то респект и уважуха:)
Была примерно такая же проблема (если блокировать мьютекс, охраняющий список подписчиков на время работы callback-ов, то callback-функция может явно или неявно вызвать подписку/отписку, что приведёт к deadlock). Тоже пришёл к копии списка подписчиков на стеке. Но о ситуации, когда подписчик отписывается, уже попав в копию, я не догадался.

Спасибо!
Чем была обусловлена реализация на Qt? Почему именно такой пример реализации?
Учитывая что паттерн может применяться в любом языке в котором возможна многопоточность следовало бы привести псевдокод или хотя бы UML диаграмму.
А в чем проблема с Qt примером?
Я лично с Qt не работал, да и в плюсах не гуру, однако пример нахожу вполне читабельным и понятным.
Я и не написал что есть какая либо проблема. Мне просто интересно почему автор решил описать всё именно на Qt, и почему нет принятой для описания паттернов UML диаграммы.
* внутри одного из потребителей объекта Worker делается захват мьютекса M (с последующим его освобождением, разумеется);
* еще один объект системы при захваченном мьютексе M регистрирует себя в качестве потребителя объекта Worker.

Проблема описана неясно:

— в общем описании дедлока фигурирует 2 мьютекса, а в описании проблемы — один (М).
— непонятно, как «еще один объект системы захваченном мьютексе M регистрирует себя в качестве потребителя», если мьютекс М уже захвачен «одним из потребителей».
— употребляется неверная терминология: мьютексы захватываются потками, а не объектами

Так что обсуждать решение непонятно какой проблемы бессмысленно.
Возможно имелось в виду что в callback будет вызван один из методов registerCallback или unregisterCallback. Хотя в описании проблемы такой ситуации нет и близко)
Очень важное пояснение: дедлок может образоваться не только из мутексов — это наиболее очевидный и распространенный вариант, о котором уже много написано и сказано. Участником цепочки дедлока может быть любая операция, которая предполагает ожидание чего-либо в другом потоке, например, ожидание завершения другого потока (pthread_join или QThread::wait — кому как привычнее) или ожидание переменной кондиции, как в рассмотренном нами примере.
Ну про то что дедлоки возникают не только из-за мутексов это понятно. Но во-первых, в изначальном описании проблемы про condition viriable вообще не было упоминания, она появилась лишь в предложенном вами решении. А во-вторых, как уже говорилось выше, ну не понятно из вашего описания, что дедлок возникает из-за вызова в колбэке одного из методов register/unregister.

Т.е. у вас тут:
>>еще один объект системы при захваченном мьютексе M регистрирует себя в качестве потребителя объекта Worker.

не сказано, что регистрация происходит в обратном вызове. Те кто имел дело с многопоточным программированием конечно это и сами поймут из контекста, но вот у новичков в этой области могут возникнуть проблемы.
Зачем это вообще нужно на Qt? Там есть сигналы/слоты, где всё уже это реализовано?
И еще, тут мы просто делаем копию контейнера, но его содержимое то не копируется, указатели то идут на те же самые колбэки, удали хоть один и всё умрет.
Я уж про стиль кода молчу, _ заревервированны под компиляторозависимые вещи. Да и с такой записью итераторов некрасиво выглядит просто, обычного foreach тут за глаза хватит.
Как уже писали выше, не ясно о каких дедлоках речь, если мутекс тут только один.

Даже если тут есть проблема и она решена, то все дедлоки в этом подходе не устранены. Коллбэк назначается одной нитью, а выполняется другой. Тут тоже возможны дедлоки на доступе к общим данным.

Подход, устраняющий все потенциальные дедлоки — использование «входящей» и «исходящей» очередей для воркера.
Очереди — это хорошо. Но иногда (как в нашем случае), требуется обеспечить синхронную отработку, т.е. после возврата из unregisterCallback мы гарантированно должны быть уверены в том, что нас уже отписали, а не просто поместили в какую-то очередь.

Паттерн с очередями мы активно используем там, где это можно. Мы его называем «гальванической развязкой»:)
Цитата: «Потому что поток объекта Worker должен пройтись по списку зарегистрированных потребителей и вызвать функцию интерфейса каждого потребителя».

Совсем непонятно с чего это он «должен» и очевидно неэффективная реализация (проходить по списку и вызывать для каждого). Уже с этого места хочется начать редизайн…
Если глянуть в официальную документацию по классу QThread, первый же комментарий в ней (внизу страницы) гласит, что наследоваться от QThread и писать код в run() некошерно, и общепринятый подход к использованию QThread — перенос в него объектов с помощью QObject::moveToThread() и использование этого класса лишь для управления нитью (start(), run() и т.д.).
Зарегистрируйтесь на Хабре, чтобы оставить комментарий