Как обнаружить и отладить многопоточные проблемы?
Это продолжение этого вопроса, где я не получил никакого ввода по этому вопросу. Вот краткий вопрос:
Можно ли обнаруживать и отлаживать проблемы, возникающие из многопоточного кода?
Часто мы должны сообщать нашим клиентам: "Мы не можем воспроизвести проблему здесь, поэтому мы не можем ее исправить. Расскажите нам о шагах по воспроизведению проблемы, тогда мы ее исправим". Это какой-то неприятный ответ, если я знаю, что это проблема многопоточности, но в основном я этого не делаю. Как узнать, что проблема - проблема многопоточности и как ее отладить?
Я хотел бы знать, есть ли какие-либо специальные фреймворки регистрации, методы отладки или инспекторы кода или что-то еще, чтобы помочь решить такие проблемы. Приветствуем общие подходы. Если какой-либо ответ должен быть связан с языком, сохраните его на .NET и Java.
Ответы
Ответ 1
Общеизвестно, что проблемы с многопоточностью/параллелизмом трудно воспроизвести - это одна из причин, по которой вам следует разрабатывать, чтобы избежать или хотя бы минимизировать вероятности. По этой причине неизменные объекты так ценны. Попробуйте изолировать изменяемые объекты в одном потоке, а затем тщательно контролировать обмен изменяемыми объектами между потоками. Попытка программировать с дизайном передачи объекта, а не "общими" объектами. В последнем случае используйте полностью синхронизированные объекты управления (о которых легче рассуждать) и избегайте использования синхронизированных объектов других объектов, которые также должны быть синхронизированы, то есть пытайтесь сохранять их самодостаточными. Ваша лучшая защита - это хороший дизайн.
Взаимные блокировки легче всего отлаживать, если вы можете получить трассировку стека, когда заблокированы. Учитывая трассировку, большинство из которых обнаруживают взаимоблокировки, легко определить причину, а затем рассуждать о коде относительно того, почему и как это исправить. В случае взаимных блокировок всегда будет проблемой получить одинаковые блокировки в разных порядках.
Живые блокировки сложнее - лучше всего наблюдать за системой, находясь в состоянии ошибки.
Расовые условия, как правило, чрезвычайно трудно воспроизвести, и еще сложнее определить их при ручной проверке кода. С этим путем, который я обычно выбираю, помимо обширного тестирования для репликации, является рассуждение о возможностях и попытка записать информацию, чтобы доказать или опровергнуть теории. Если у вас есть прямые доказательства коррупции в государстве, вы можете рассуждать о возможных причинах, основанных на коррупции.
Чем сложнее система, тем сложнее находить ошибки параллелизма и рассуждать о ее поведении. Используйте такие инструменты, как JVisualVM и профилировщики удаленного подключения - они могут спасти жизнь, если вы можете подключиться к системе в состоянии ошибки и проверить потоки и объекты.
Также обратите внимание на различия в возможном поведении, которые зависят от количества ядер ЦП, конвейеров, пропускной способности шины и т.д. Изменения в оборудовании могут повлиять на вашу способность воспроизвести проблему. Некоторые проблемы будут отображаться только на одноядерных процессорах, другие только на многоядерных.
И последнее: попробуйте использовать объекты параллелизма, распространяемые вместе с системными библиотеками - например, в Java ваш друг - java.util.concurrent
. Написание собственных объектов управления параллелизмом сложно и чревато опасностью; оставьте это экспертам, если у вас есть выбор.
Ответ 2
Я думал, что ответ, который вы получили на ваш другой вопрос, был довольно хорош. Но я остановлюсь на этих моментах.
Только изменение общего состояния в критическом разделе (взаимное исключение)
Приобретайте блокировки в заданном порядке и освобождайте их в обратном порядке.
Использовать заранее созданные абстракции, когда это возможно (Как и материал в java.util.concurrent)
Кроме того, некоторые инструменты анализа могут выявлять некоторые потенциальные проблемы. Например, FindBugs может найти некоторые проблемы с потоками в программах Java. Такие инструменты не могут найти все проблемы (они не серебряные пули), но они могут помочь.
Как vanslly указывает на комментарий к этому ответу, изучение хорошо размещенного вывода журнала также может очень полезно, но остерегайтесь Heisenbugs.
Ответ 3
Предполагая, что у меня есть сообщения о проблемах, которые трудно воспроизвести, я всегда нахожу их, читая код, предпочтительно, используя чтение пара-кода, поэтому вы можете обсудить потребности семантики/блокировки потоков. Когда мы делаем это на основе сообщенной проблемы, я считаю, что мы всегда сталкиваемся с одной или несколькими проблемами довольно быстро. Я думаю, что это также довольно дешевая техника для решения сложных проблем.
Извините за невозможность сказать вам нажать ctrl + shift + f13, но я не думаю, что есть что-то подобное. Но просто думать о том, что сообщение о проблеме на самом деле обычно дает довольно сильное чувство направления в коде, так что вам не нужно начинать с main().
Ответ 4
В дополнение к другим хорошим ответам, которые вы уже получили: всегда проверяйте на машине с по меньшей мере столько процессоров/процессорных ядер, сколько клиент использует, или как в вашей программе есть активные потоки. В противном случае некоторые многопоточные ошибки могут быть трудно воспроизвести.
Ответ 5
Помимо аварийных дампов, техникой является обширное ведение журнала во время выполнения: где каждый поток регистрирует, что он делает.
Первый вопрос, когда сообщается об ошибке, может быть: "Где файл журнала?"
Иногда вы можете увидеть проблему в файле журнала: "Этот поток обнаруживает здесь незаконное/неожиданное состояние... и смотри, этот другой поток делал это, как раз перед, так и после этого".
Если файл журнала не говорит, что происходит, извинитесь перед клиентом, добавьте в код достаточно много дополнительных протоколирующих операторов, дайте новый код клиенту и скажите, что вы его исправите после того, как это произойдет еще раз.
Ответ 6
Для Java существует инструмент проверки под названием javapathfinder, который, по моему мнению, полезен для отладки и проверки многопоточного приложения на предмет возможных состояний гонки и ошибок блокировки из кода.
Он отлично работает как с Eclipse, так и с IDE Netbean.
[2019] хранилище githubhttps://github.com/javapathfinder
Ответ 7
Иногда многопоточные решения нельзя избежать. Если есть ошибка, ее необходимо исследовать в режиме реального времени, что почти невозможно для большинства инструментов, таких как Visual Studio. Единственное практическое решение - писать трассы, хотя сама трассировка должна:
- не добавлять задержку
- не использовать блокировку
- быть многопоточным безопасным.
- проследите, что произошло в правильной последовательности.
Это звучит как невозможная задача, но ее легко достичь, записав трассировку в память. В С# он будет выглядеть примерно так:
public const int MaxMessages = 0x100;
string[] messages = new string[MaxMessages];
int messagesIndex = -1;
public void Trace(string message) {
int thisIndex = Interlocked.Increment(ref messagesIndex);
messages[thisIndex] = message;
}
Метод Trace() является многопоточным безопасным, не блокирующим и может быть вызван из любого потока. На моем ПК требуется около 2 микросекунд, что должно быть достаточно быстрым.
Добавьте инструкции Trace(), где бы вы ни думали, что-то может пойти не так, пусть запущена программа, дождитесь появления ошибки, остановите трассировку и затем исследуйте трассировку для любых ошибок.
Более подробное описание этого подхода, который также собирает информацию о потоке и времени, перерабатывает буфер и выводит трассировку, которую вы можете найти:
CodeProject: отладка многопоточного кода в режиме реального времени 1
Ответ 8
Небольшая диаграмма с некоторыми методами отладки, которые нужно учитывать при отладке многопоточного кода.
График растет, пожалуйста, оставляйте комментарии и советы для добавления.
(обновить файл на эта ссылка)
![Multithreaded debugging chart]()
Ответ 9
Visual Studio позволяет вам проверять стек вызовов каждого потока, и вы можете переключаться между ними. Это отнюдь не достаточно для отслеживания всех видов проблем с потоками, но это начало. Для предстоящего VS2010 запланировано много улучшений для многопоточной отладки.
Я использовал WinDbg + SoS для проблем с потоками в .NET-коде. Вы можете проверить блокировки (sync blokcs), стеки вызовов потоков и т.д.
Ответ 10
Блог Tess Ferrandez содержит хорошие примеры использования WinDbg для отладки тупиков в .NET.
Ответ 11
assert() - ваш друг для определения условий гонки. Всякий раз, когда вы входите в критический раздел, утверждайте, что связанный с ним инвариант является истинным (для чего CS). Хотя, к сожалению, проверка может быть дорогостоящей и, следовательно, не подходит для использования в производственной среде.
Ответ 12
Я реализовал инструмент vmlens для определения условий гонки в программах Java во время выполнения. Он реализует алгоритм eraser.
Ответ 13
Разработайте код который принцесса рекомендовала для вашего другого вопроса (Неизменяемые объекты и передача сообщения в стиле Эрланг). Будет легче обнаружить проблемы многопоточности, потому что взаимодействия между потоками будут хорошо определены.
Ответ 14
Я столкнулся с проблемой нити, которая давала ТАМЫЙ неправильный результат и не вела себя непредсказуемо, поскольку каждый раз другие условия (память, планировщик, загрузка) были более или менее одинаковыми.
По моему опыту, я могу сказать, что HARDEST PART - это признать проблему с потоком, и BEST SOLUTION - внимательно рассмотреть многопоточный код. Просто взглянув на код потока, вы должны попытаться выяснить, что может пойти не так. Другие пути (дамп потока, профайлер и т.д.) Будут вторыми.
Ответ 15
Ограничьте функции, которые вызываются, и исключите, что можно, а что нельзя. Когда вы обнаружите фрагменты кода, которые, как вы подозреваете, могут вызывать проблему, добавьте к ней множество подробных журналов/трассировок. Когда проблема возникнет снова, просмотрите журналы, чтобы увидеть, как код выполняется не так, как в "базовых" ситуациях.
Если вы используете Visual Studio, вы также можете установить точки останова и использовать окно Parallel Stacks. Parallel Stacks - это огромная помощь при отладке параллельного кода, которая дает вам возможность переключаться между потоками для их независимой отладки. Больше info-
https://docs.microsoft.com/en-us/visualstudio/debugger/using-the-parallel-stacks-window?view=vs-2019
https://docs.microsoft.com/en-us/visualstudio/debugger/walkthrough-debugging-a-parallel-application?view=vs-2019
Ответ 16
Я использую GNU и использую простой script
$more gdb_tracer
b func.cpp:2871
r
#c
while (1)
next
#step
end
Ответ 17
Лучшее, что я могу придумать, - избегать многопоточного кода, когда это возможно. Кажется, очень мало программистов, которые могут писать многопоточные приложения без ошибок, и я бы сказал, что нет кодеров, которые могли бы писать безболезненные большие многопоточные приложения.