Ни для кого не секрет, наличие инструментов делает жизнь проще. Тезис
этот находит подтверждение как в повседневной жизни – чистить одежду удобнее и
эффективнее щеткой, чем руками, заворачивать гайки гораздо проще ключом, чем
пальцами – так и в профессиональной деятельности – кто сейчас возьмется строить,
ладно, даже не дом, пусть всего лишь баню, при помощи одного лишь топора.
Естественно программисты – не исключение. Значение инструментов, облегчающих
путь от анализа постановки задачи до получения решения, готового к внедрению,
трудно переоценить. Данный документ представляет собой краткое описание
инструмента отладки параллельных программ, разработанного корпорацией Intel и носящего название
Intel® Thread Checker.
В первом разделе
документа приводится назначение рассматриваемого инструмента, характеризуются
области его возможного применения. Во втором дается краткая характеристика
принципов работы Intel®
Thread
Checker. В
третьем и четвертом разделах приводится информация, необходимая для подготовки
пользовательского проекта и инструмента для анализа. Пятый раздел посвящен
вопросам сбора и анализа данных, полученных в результате работы Intel® Thread Checker. В шестом возможности
инструмента рассмотрены на простом примере, входящем в поставку Intel® Thread Checker.
Итак,
приступим.
Назначение Intel Thread Checker
Процесс отладки в общем
случае можно разбить на следующие шаги:
-
определение факта наличия ошибки;
-
поиск (локализация) ошибки;
-
выяснение причин ошибки;
-
определение способа устранения ошибки;
-
устранение ошибки.
Кажется, что на первом
шаге никакой инструмент не требуется. Запускаем программу и либо на некоторых
исходных данных получаем неверные результаты, либо обнаруживаем, что некоторая
последовательность действий по использованию программы ведет к ее «падению» или
«зависанию». Однако для параллельной программы даже этот очевидный шаг может
иметь существенную сложность. На практике нередко встречаются ситуации, когда
неработоспособность параллельной программы проявляется один раз на сотню и более
запусков. Очевидно, в этом случае инструментальная поддержка лишней не
будет.
Назначение Intel® Thread Checker (ITC) – поиск мест с возможным
недетерминированным поведением многопоточной программы, написанной как на основе
библиотеки потоков (Windows или POSIX threads), так и с
использование технологии OpenMP. Соответственно ITC может быть
использован как под операционными системами семейства Windows, так и под различными ОС
семейства Linux.
Принципы поиска ошибок рассмотрены ниже, здесь же укажем, что ITC неплохо
справляется с задачей обнаружения факта ошибки в программе, даже если эта ошибка
в текущем варианте исполнения программы и не проявила себя.
Второй шаг – поиск
ошибки заключается в необходимости как можно более точной ее локализации, в
идеале должна быть найдена переменная с неверным значением и/или строка кода,
ведущая к краху программы. Типичный метод работы в этом пункте – использование
режима трассировки в отладчике с наблюдением за состоянием переменных,
регистров, стека вызова и т.д. «Плохая новость» – для многопоточных программ
режим трассировки практически неприменим, поскольку автоматически меняет
характер их выполнения, а значит, скрывает места, которые могут приводить к
проблемам во время реальной работы. Кстати говоря, даже типичный способ
локализации ошибки расстановкой операторов печати по тексту программы в этом
случае нужно использовать с большой осторожностью – печать также вносит
синхронизацию в выполнение программы.
Что же делать? Быть
может, наилучшее из возможных решение реализовано в ITC. ITC не есть привычный всем отладчик с
режимами трассировки, наблюдения и т.д. ITC выполняет анализ программы сам, без
участия программиста, причем анализируется не только выполненный «прогон»
программы, а все возможные варианты ее выполнения. В результате выясняются и
показываются программисту места в программе, в которых содержатся ошибки (с той
или иной долей вероятности, в большинстве случаев близкой к 100%).
Шаг третий – выяснение
причин ошибки. Задача здесь – понять, почему ошибка возникла. Отсюда во многих
случаях автоматически вытекает способ ее устранения (задача шага четвертого).
Можно, конечно, выяснить условия, ведущие к проявлению ошибки (некое сочетание
значений переменных, например) и просто вставить в код заплатку именно для этого
случая. Данный вариант мы здесь не рассматриваем. На этом шаге ITC помогает тем,
что каждое найденное им проблемное место сопровождает комментарием, содержащим
тип ошибки: гонка данных, несинхронизированный доступ к переменной, тупик и
т.д.
В результате мы получаем
место потенциальной ошибки, переменную, с которой связана проблема и описание
ошибки. Остается лишь освоить типовые способы борьбы с типовыми ошибками и
значительная их часть будет находиться и исправляться без грандиозных
усилий.
Единственное в чем ITC
совсем не может помочь – это шаг пятый. Устранять найденную ошибку все-таки
придется программисту самостоятельно.
Возможности Intel Thread Checker
Согласно [2] ITC
обнаруживает ошибки следующих видов: гонки данных
(data
races), тупики (deadlocks), потоки в состоянии ожидания (stalled
threads), потерянные сигналы (lost signals), заброшенные замки (abandoned
locks).
Приведем краткое
описание каждого вида.
-
Гонки
данных. Возникают, когда несколько потоков работают с разделяемыми данными и
конечный результат зависит от соотношения скоростей потоков. Пусть, например,
один поток выполняет над общей переменной x операцию
x = x + 3, а
второй поток – операцию
x = x + 5. Данные
операции для каждого потока фактически разбиваются на три отдельных подоперации:
считать x из памяти,
увеличить x, записать
x в память. В
зависимости от взаимного порядка выполнения потоками подопераций финальное
значение переменной x может быть больше исходного на 3, 5 или 8.
Гонка данных возможна и в случае, когда один поток пишет в переменную, а
остальные только читают из нее.
-
Тупики. Взаимная блокировка
потоков, ожидающих наступление некоторого события для продолжения работы.
Типичный пример тупика, когда нулевой поток занял для использования ресурс 1 и
ожидает предоставления ему ресурса 2, а первый поток занял ресурс 2 и ожидает
предоставления ему ресурса 1.
-
Потоки
в состоянии ожидания.
Одно из состояний потока в многозадачной операционной системе – ожидание.
Поток переходит в него, когда для продолжения выполнения ему требуется
наступление некоторого внешнего события. Если пребывание потока в этом
состоянии продолжается слишком долго, ITC рапортует об ошибке типа stalled
thread. Интервал времени, по истечении которого выдается данная диагностика,
может быть задан в настройках ITC.
-
Потерянные сигналы. Возникают,
когда поток ожидает наступление некоторого события, произошедшего прежде, чем
поток пришел в состояние готовности к его приему и обработке. В результате
поток никогда не сможет выйти из состояния ожидания.
-
Заброшенные замки. Возникают в
ситуации, когда поток захватил некоторый ресурс (критическую секцию, мьютекс)
и был снят с выполнения по той или иной причине. В результате ресурс не может
быть освобожден. Если он требуется другому потоку, это приведет к бесконечному
ожиданию.
Принцип сбора информации
Анализ программы,
выполняемый ITC, основан на процедуре инструментации. Инструментация – вставка обращений к библиотеке ITC для записи действий,
потенциально способных привести к ошибкам: работа с памятью, вызовы операций
синхронизации и работа с потоками [2]. Может выполняться автоматически на уровне
исполняемого модуля (а также dll-библиотеки) и/или по указанию программиста на
уровне исходного кода. Для достоверности получаемых результатов крайне
желательно, чтобы во время сборки анализируемой программы была выключена
оптимизация (сборка в конфигурации debug не обязательна).
В процессе анализа
контролируется:
-
доступ к памяти;
-
операции синхронизации;
-
операции создания потоков.
Необходимо отметить, что
неисполняемые участки (не вызываемые функции, ветки условных переходов и т.д.)
никак не проверяются, то есть под анализ не подпадают.
Подготовка программы для анализа
Использование отладчика
Intel® Thread Checker возможно в двух режимах:
-
Бинарная инструментация
программы – осуществляется автоматически в момент запуска Активности[1]
(Activity) в
проекте ITC. Рекомендуется в случае, если отсутствует доступ к исходным кодам
или невозможна повторная сборка программы с нужными ITC
настройками.
-
Компиляторная
инструментация – при сборке анализируемой программы необходимо
указать ключ компилятора /Qtcheck. Позволяет ITC предоставить информацию о
найденных ошибках с указанием имен переменных, с которыми эти ошибки
связаны.
-
Сборка приложения для работы с ITC предполагает установку
следующих опций проекта (или настроек в make-файле):
-
Компиляция потоко-безопасного кода: -MT[d], -MD[d]. Данные опции автоматически
устанавливаются при сборке в конфигурации debug. При сборке в конфигурации release указанные опции необходимо устанавливать
вручную.
-
Использование debug опций: -Z[i,I,7], -Od. Замечание
аналогичное предыдущему пункту.
-
Связывание с ключом /fixed:no.
Необходимо указывать явно.
Дополнительно необходимо
отметить, что при использовании ключа /Qtcheck в среде разработки (IDE) требуется указать путь к
библиотекам ITC. Обычно этот путь имеет вид C:\Program Files\Intel\VTune\Analyzer\Lib\.
Создание проекта в Intel Thread
Работа в ITC выполняется
в рамках проекта. Для его создания
используется команда меню File→New
Project. В главном окне мастера настройки проекта (см. рис. 1)
необходимо выполнить всего лишь два действия. Первое – указать исполняемый файл
(Launch an application). Второе
(необязательное) – указать
аргументы командной строки.

Рис. 1.
Мастер настройки проекта в Intel Thread Checker
(версия 3.0)
Рекомендуется при анализе
программы, с одной стороны, использовать типичные размеры обрабатываемых данных,
с другой, задавать их так, чтобы программа «убиралась» в оперативную память с
учетом накладных расходов ITC, которые могут быть довольно
значительными.
Сбор и анализ данных
После запуска в проекте
ITC активности
(при создании проекта это происходит автоматически) начинается инструментация
исполняемого модуля, указанного для анализа, и используемых им динамических
библиотек. Затем модуль запускается и начинается процесс анализа. По завершении
ITC формирует окно с информацией о найденных ошибках и подозрительных местах.
Возможный его вид указан на рис. 2.
В случае повторного
запуска активности необходимо использовать один из следующих вариантов: 1)
выбрать пункт меню Activity→Run, 2)
нажать F5, 3) нажать кнопку
на панели
инструментов.

Рис. 2.
Результат анализа – список
диагностики
По каждой диагностике, выданной
ITC, в случае если сборка выполнялась с приведенными в разделе 4 настройками,
может быть получена дополнительная информация (см. раздел 7), вид которой
показан на рис. 3.

Рис. 3.
Результат анализа – диагностика в исходном
коде