Недавно я работал над проектами, которые интенсивно используют многопоточность. Я думаю, что я в порядке при разработке их; максимально использовать дизайн без сохранения состояния, блокировать доступ ко всем ресурсам, которые нужны более чем одному потоку, и т. д. Мой опыт в функциональном программировании очень помог.
Однако, читая чужой кодовый поток, я запутываюсь. Я сейчас отлаживаю тупик, и, поскольку стиль и дизайн кодирования отличаются от моего личного стиля, мне трудно увидеть потенциальные условия тупика.
Что вы ищете при отладке тупиков?
debugging
multithreading
Майкл К
источник
источник
Ответы:
Если ситуация является реальной тупиковой ситуацией (то есть два потока удерживают две разные блокировки, но по крайней мере один поток хочет блокировки, которую удерживает другой поток), вам необходимо сначала отказаться от всех предварительных представлений о том, как потоки упорядочивают блокировку. Ничего не предполагать Возможно, вы захотите удалить все комментарии из кода, который вы просматриваете, поскольку эти комментарии могут заставить вас поверить в то, что не соответствует действительности. Трудно подчеркнуть это достаточно: ничего не предполагайте.
После этого определите, какие блокировки удерживаются, пока поток пытается заблокировать что-то еще. Если вы можете, убедитесь, что поток разблокируется в обратном порядке от блокировки. Более того, убедитесь, что поток удерживает только одну блокировку за раз.
Кропотливо прорабатывает выполнение потока и проверяет все события блокировки. При каждой блокировке определите, удерживает ли поток другие блокировки, и если да, то при каких обстоятельствах другой поток, выполняя аналогичный путь выполнения, может добраться до рассматриваемого события блокировки.
Вполне возможно, что вы не найдете проблемы, пока у вас не хватит времени или денег.
источник
Как уже говорили другие ... если вы можете получить полезную информацию для регистрации, попробуйте сначала, так как это проще всего сделать.
Определите замки, которые участвуют. Поменяйте все мьютекс / семафоры, которые ждут вечно на время ожидания ... что-то смехотворно длинное, как 5 минут. Зарегистрируйте ошибку, когда она истечет. Это, по крайней мере, укажет вам направление на один из замков, который вовлечен в проблему. В зависимости от изменчивости времени вам может повезти, и вы найдете оба замка после нескольких пробежек. Используйте код / условия ошибки функции для записи трассировки псевдостека после того, как таймерное ожидание не сможет определить, как вы попали туда в первую очередь. Это должно помочь вам определить поток, который вовлечен в проблему.
Еще одна вещь, которую вы можете попробовать - это создать библиотеку-оболочку вокруг ваших сервисов mutex / семафоров. Отследите, какие потоки имеют каждый мьютекс и какие потоки ожидают мьютекс. Создайте поток монитора, который проверяет, как долго блокировались потоки. Запустите в течение некоторого разумного периода времени и сохраните информацию о состоянии, которую вы отслеживаете.
В какой-то момент потребуется проверка старого старого кода.
источник
Первый шаг (как говорит Петер) - это регистрация. Хотя по моему опыту это часто проблематично. При тяжелой параллельной обработке это часто невозможно. Однажды мне пришлось отлаживать что-то похожее с нейронной сетью, которая обрабатывала 100 тыс. Узлов в секунду. Ошибка произошла только через несколько часов, и даже одна строка вывода настолько сильно замедлила процесс, что это заняло бы несколько дней. Если ведение журнала возможно, сконцентрируйтесь не столько на данных, сколько на потоке программы, пока не узнаете, в какой части это происходит. Просто простая строка в начале каждой функции, и если вы можете найти правильную функцию, разбейте ее на более мелкие куски.
Другой вариант - удаление частей кода и данных для локализации ошибки. Может быть, даже написать небольшую программу, которая берет только некоторые из классов и выполняет только самые основные тесты (конечно, в нескольких потоках). Удалите все, что связано с графическим интерфейсом, например, любой вывод о фактическом состоянии обработки. (Я обнаружил, что пользовательский интерфейс является источником ошибки достаточно часто)
В своем коде постарайтесь проследить полный логический поток управления между инициализацией блокировки и ее снятием. Распространенной ошибкой может быть блокировка в начале функции, разблокировка в конце, но где-то посередине условный оператор возврата. Исключения также могут помешать выпуску.
источник
Моими лучшими друзьями были операторы печати / журнала в интересных местах кода. Они обычно помогают мне лучше понять, что на самом деле происходит внутри приложения, без нарушения синхронизации между различными потоками, что может помешать воспроизведению ошибки.
Если это не помогает, мой единственный оставшийся метод - смотреть на код и пытаться создать ментальную модель различных потоков и взаимодействий, а также пытаться придумать возможные сумасшедшие способы достижения того, что, по-видимому, произошло :-) Но я не считаю себя очень опытным убийцей тупиков. Надеюсь, другие смогут дать лучшие идеи, из которых я тоже могу учиться :-)
источник
Прежде всего, попробуйте найти автора этого кода. Вероятно, он поймет, что он написал. даже если вы двое не можете точно определить проблему, просто поговорив, по крайней мере, вы можете сесть с ним, чтобы определить тупиковую часть, которая будет намного быстрее, чем вы поймете его / ее код без посторонней помощи.
Если это не удастся, как сказал Петер Тёрёк, то, вероятно, подойдет регистрация. Насколько я знаю, Debugger плохо работал в многопоточной среде. попытайтесь определить, где находится замок, узнать, какие ресурсы ждут, и в каком состоянии происходит гонка.
источник
Этот вопрос меня привлекает;) Прежде всего, считайте, что вам повезло, поскольку вы смогли воспроизводить проблему последовательно при каждом запуске. Если вы получаете одно и то же исключение с одной и той же трассировкой стека каждый раз, то это должно быть довольно просто. Если нет, то не стоит так сильно доверять трассировке стека, вместо этого просто следите за доступом к глобальным объектам и их изменениям состояния во время выполнения.
источник
Если вам нужно отладить тупики, у вас уже есть проблемы. Как правило, используйте замки в кратчайшие сроки - или не используйте их вообще, если это возможно. Следует избегать любой ситуации, когда вы берете блокировку и затем переходите к нетривиальному коду.
Конечно, это зависит от вашей среды программирования, но вы должны смотреть на такие вещи, как последовательные очереди, которые могут позволить вам получить доступ к ресурсу только из одного потока.
И затем есть старая, но безошибочная стратегия: назначьте «уровень» каждой блокировке, начиная с уровня 0. Если вы берете блокировку уровня 0, вам не разрешают другие блокировки. После взятия блокировки уровня 1 вы можете взять блокировку уровня 0. После взятия замка уровня 10 вы можете взять замки на уровне 9 или ниже и т. Д.
Если вы обнаружите, что это невозможно сделать, вам нужно исправить свой код, потому что вы попадете в тупики.
источник