Тестирование многопоточных условий гонки

54

Читая комментарии к этому ответу , а именно:

Тот факт, что вы не можете написать тест, не означает, что он не сломан. Неопределенное поведение, которое обычно работает должным образом (C и C ++ полны этого), условия гонки, потенциальное переупорядочение из-за слабой модели памяти ... - CodesInChaos 7 часов назад

@CodesInChaos, если он не может быть воспроизведен, то код, написанный для исправления, также не может быть протестирован. И, по моему мнению, использование непроверенного кода в жизни - худшее преступление - RhysW 5 часов назад

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

Дэн Нили
источник
1
пошаговая (сборка) инструкция по инструкции на обоих концах
трещотка урод
1
Статический анализ часто может показать потенциал UB, неясно, считается ли это как тест, хотя
jk.
Извините, что спросил, но что означает «UB»?
Даг
2
Хороший вопрос, мне было бы интересно увидеть возможные решения этого.
RhysW
1
@Doug Неопределенное поведение, которое может включать в себя, но не ограничено, условия гонки
jk.

Ответы:

85

Будучи в этом сумасшедшем бизнесе примерно с 1978 года, проведя почти все это время во встроенных вычислениях в реальном времени, работая в многозадачных, многопоточных, многозадачных системах, иногда с несколькими физическими процессорами, погнавшись за мою долю гонок. Условия, мое взвешенное мнение, состоит в том, что ответ на ваш вопрос довольно прост.

Нет.

Нет хорошего общего способа вызвать состояние гонки при тестировании.

Ваша ЕДИНСТВЕННАЯ надежда состоит в том, чтобы создать их полностью из вашей системы.

Когда и если вы обнаружите, что кто-то вставил его, вы должны заколоть его муравейником, а затем перепроектировать, чтобы устранить его. После того, как вы разработали его фальшивку (произносится как f *** up) из вашей системы, вы можете освободить его от муравьев. (Если муравьи уже поглотили его, оставив только кости, поставьте табличку с надписью «Это то, что происходит с людьми, которые ставят гоночные условия в проект XYZ!» И оставьте его там.)

Джон Р. Штром
источник
22
Я полностью согласен. Другими словами, это очень похоже на шутку. Пациент: «Доктор, мне больно, когда я делаю это ...» Доктор: «Тогда прекрати!»
Марк Рушаков
Хороший ответ. Если что-то вызывает непроверяемую проблему, попробуйте сначала обойти это, избегайте проблемы в целом!
RhysW
Мой единственный вопрос: какой большой муравейник я должен использовать? (+1 КСТАТИ)
Питер К.
15
+1 за правильное произношение faux pas . (И остальной ответ.)
Blrfl
1
@PeterK., Это один из тех немногих случаев , в разработке программного обеспечения, наряду с мониторами, оперативной памятью и жесткими дисками, где больше IS лучше.
Джон Р. Штром
16

Если вы находитесь в цепочке инструментов MS. Ms Research создала инструмент, который будет вызывать новые чередования для каждого пробега и может воссоздавать неудачные пробежки, которые называются шахматами .

вот видео, показывающее это в использовании.

повторный показ
источник
5
Это выглядит впечатляюще; Я должен найти время, чтобы попробовать это в какой-то момент.
Дэн Нили,
16

Лучший инструмент, который я знаю для такого рода проблем, - это расширение Valgrind под названием Helgrind .

По сути, Valgrind моделирует виртуальный процессор и запускает ваш двоичный файл (без изменений) поверх него, поэтому он может проверять каждый доступ к памяти. Используя эту структуру, системные вызовы Helgrind watch делают вывод, что доступ к общей переменной не защищен должным образом механизмом взаимного исключения. Таким образом, он может обнаружить теоретическое состояние расы, даже если это на самом деле не произошло.

Intel продает очень похожий инструмент под названием Intel Inspector .

Эти инструменты дают отличные результаты, но ваша программа будет значительно медленнее во время анализа.

Julien
источник
1
Valgrind все еще инструмент * nix only?
Дэн Нили,
1
Да, Linux, MacOSX, Android и некоторые BSD: valgrind.org/info/platforms.html
Julien,
1
ThreadSanitizer - аналогичный инструмент. Он работает не так, как Helgrind, что дает преимущество в том, что он намного быстрее, но требует интеграции в набор инструментов.
Себастьян Редл
7

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

Хороший трюк описан Ярославом Тулачем в « Практическом API-дизайне» : если у вас есть операторы логирования в рассматриваемом коде, манипулируйте потребителем этих операторов логирования (например, внедренным псевдотерминалом), чтобы он принимал отдельные сообщения журнала в определенном заказ на основе их содержания. Это позволяет вам контролировать чередование шагов в разных потоках без необходимости добавлять что-либо в производственный код, которого там еще нет.

Килиан Фот
источник
2
Я проделал аналогичную работу, прежде чем использовать внедренный репозиторий, чтобы усыплять потоки, вызывающие его, в определенных порядках, чтобы вызвать желаемое чередование. Написав код, который делает это, я склонен к +1 @ ответу Джона выше. Серьезно, этот материал настолько болезнен, чтобы правильно его использовать, и все же дает только самые точные гарантии догадок, потому что могут быть немного разные чередования с разными результатами; лучший подход состоит в том, чтобы просто устранить все возможные состояния гонки с помощью статического анализа или тщательного комбинирования кода для любого общего состояния
Джимми Хоффа,
6

Невозможно быть абсолютно уверенным, что различные типы неопределенного поведения (в частности, в условиях гонки) не существуют.

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

Несколько интересных инструментов для этой цели:

Valgrind это проверка памяти. Он обнаруживает утечки памяти, считывает неинициализированную память, использует висячие указатели и доступ за пределы.

Helgrind - это средство проверки безопасности потоков. Он находит условия гонки.

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

UBSan - это неопределенная проверка поведения. Он находит различные случаи неопределенного поведения C и C ++, такие как целочисленные переполнения, сдвиги вне диапазона и тому подобное.

MSan это проверка памяти. У него такие же цели, как у Valgrind.

TSan - это проверка безопасности потока. У него те же цели, что и у Хелгринда.

Эти три встроены в компилятор Clang и генерируют код во время компиляции. Это означает, что вам нужно интегрировать их в процесс сборки (в частности, вам необходимо скомпилировать с Clang), что делает их первоначальную настройку намного сложнее, чем * grind, но, с другой стороны, они имеют гораздо меньшие накладные расходы времени выполнения.

Все перечисленные инструменты работают на Linux, а некоторые на MacOS. Я не думаю, что какая-либо работа над Windows пока надежна.

Себастьян Редл
источник
1

Кажется, что большинство ответов здесь ошибочно принимают этот вопрос как «как автоматически определять условия гонки?» когда вопрос действительно "как мне воспроизвести условия гонки при тестировании, когда я их найду?"

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

Я использую node.js и mongo, где некоторые действия включают создание согласованных данных в нескольких коллекциях. В этих случаях мои модульные тесты будут вызывать приложение, чтобы сказать ему «настроить ожидание события X», и после того, как приложение установит его, будет запущен тест для события X, а затем тесты сообщат приложение («Я закончил с ожиданием события X»), поэтому остальные тесты будут работать нормально.

Ответ здесь подробно объясняет этот тип вещей в контексте Python: https://stackoverflow.com/questions/19602535/how-can-i-reproduce-the-race-conditions-in-this-python-code- надежно

BT
источник