поддерживаю выше заданые вопросы
|
Введение в использование инструментов для параллельного программирования на примере пакета Intel Parallel Studio
6.7. Пример использования Intel Thread Checker
Для начального знакомства с ITC рассмотрим пример, входящий в поставку инструмента версии 3.0 и описанный в [3].
6.7.1. Описание примера
Наличие у каждого потока в многопоточном приложении доступа к общему ВАП2 процесса позволяет потокам эффективно обмениваться данными, с одной стороны, и является основным источником ошибок, с другой. Гонки данных, они же конфликты доступа (storage conflicts), поджидают зазевавшегося про- граммиста буквально на каждом шагу. Поиск таких ошибок методом "пристального взгляда" – задача весьма сложная.
Рассматриваемый пример создает 4 потока, каждый из которых увеличивает значение общей глобальной переменной globalX и использует критическую секцию для синхронизации доступа к ней. Однако, несмотря на наличие критической секции, в коде все-таки содержится конфликт доступа. Нам предстоит его обнаружить, выяснить причину и исправить ситуацию.
6.7.2. Изучение примера
Откройте проект DataRaces, последовательно выполняя следующие шаги:
- запустите приложение Microsoft Visual Studio 2005;
- в меню File выполните команду Open → Project/Solution…;
- в диалоговом окне Open Project выберите папку C:\ITCLabs\DataRaces;
- дважды щелкните на файле DataRaces.dsw или, выбрав файл, выполните команду Open;
- согласитесь с предложением конвертировать проект DataRaces.dsp в новый формат.
После открытия проекта в окне Solution Explorer дважды щелкните на файле исходного кода DataRaces.с, как это показано на рис. 6.4. После этих действий программный код, с которым предстоит работать, будет открыт в редакторе кода Microsoft Visual Studio 2005.
Интерес в этом небольшом файле представляет потоковая функция increment.
int globalX = 0; DWORD WINAPI increment (void *arg) { CRITICAL_SECTION cs; InitializeCriticalSection (&cs); EnterCriticalSection (&cs); globalX++; LeaveCriticalSection (&cs); DeleteCriticalSection (&cs); return 0; }
На первый взгляд код выглядит корректно. Доступ к переменной globalX защищен. Посмотрим, что покажет запуск программы.
Соберите и запустите пример, выполнив следующие действия:
- в меню Build выполните команду Build Solution;
- проигнорируйте предупреждение компилятора Microsoft о неизвестной опции /Qtcheck;
- в меню Debug выполните команду Start Without Debugging. Убедитесь, что вывод на экран соответствует представленному на рис. 6.5.
Кажется, все верно. Число потоков равно четырем. Начальное значение переменной globalX, как нетрудно убедиться, равно нулю. Таким образом, результат верен. Повторите запуск программы несколько раз и убедитесь, что результат стабильно равен 4.
Может быть, программа корректна? Подумаем, что нужно для того, чтобы гонки данных могли проявиться. Необходимо, чтобы имел место разный порядок выполнения потоков во времени. Посмотрим, возможно ли это в данном примере. Конечно же, нет! Вот участок функции main, запускающий потоки:
for (i = 0; i < NTHREADS; i++) { h[i] = CreateThread (0, 0, increment, NULL, 0, NULL); }
Нетрудно понять, что потоки создаются последовательно, по мере выполнения цикла. Из-за крайней простоты, а значит, и малого времени выполнения пото- ковой функции скорее всего и работа потоков происходит последовательно. Конфликт доступа к переменной globalX, даже если он имеет место, не успевает проявиться.
Что ж, изменим немного код. Пусть каждый поток увеличивает значение переменной globalX не один раз, а многократно.
DWORD WINAPI increment (void *arg) { int i; CRITICAL_SECTION cs; InitializeCriticalSection (&cs); for (i = 0; i < 10000; i++) { EnterCriticalSection (&cs); globalX++; LeaveCriticalSection (&cs); } DeleteCriticalSection (&cs); return 0; }
Добавьте в код выделенные в предыдущем фрагменте строки, соберите и запустите пример. Убедитесь, что вывод на экран соответствует представленному на рис. 6.6.
Не правда ли, неожиданно?! Вместо 40000 на экране совсем другое число! Повторите запуск несколько раз и убедитесь, что результат работы программы будет меняться. Типичная ситуация для гонки данных.
Итак, приложив некоторые усилия, мы прошли два этапа в рассмотренном в п. 1.1 процессе отладки. Наличие ошибки обнаружено, локализация ее также не вызывает сложностей в силу малого размера кода в примере. Осталось понять, в чем же причина ошибки. Но прежде посмотрим, как с ее обнаружением справится ITC.
6.7.3. Подготовка программы для анализа
Выполните следующие действия для подготовки программы к анализу.
- Верните код примера к исходному состоянию.
- Конвертируйте проект для использования компилятора Intel® C++ Compiler. В окне Solution Explorer выберите файл проекта, щелкните правой кнопкой мыши и в контекстном меню выполните команду Convert to use Intel® C++ Project System.
- В меню Project выберите пункт Properties, в появившемся окне настроек проекта в дереве слева выберите узел Configuration Properties →C/C++ →General (рис. 6.7). В открывшейся таблице справа убедитесь, что значение поля Debug Information Format равно Program Database (/Zi).
- В дереве слева выберите узел Configuration Properties→C/C++→Optimization (рис. 6.8). В открывшейся таблице справа убедитесь, что значение поля Optimization равно Disabled(/Od).
- В дереве слева выберите узел Configuration Properties→C/C++→Code Generation (рис. 6.9). В открывшейся таблице справа убедитесь, что значение поля Runtime Library установлено в Multi-threaded Debug DLL (/MDd).
- В дереве слева выберите узел Configuration Properties→Linker→Command Line (рис. 6.10). Убедитесь, что сборка программы выполняется с использованием опции компоновщика /FIXED:NO.
- Убедитесь, что включена компиляторная инструментация кода. В дереве слева выберите узел Configuration Properties→C/C++→Command Line (рис. 6.11). Убедитесь, что в поле Additional Options установлен ключ компилятора /Qtcheck
После выполнения указанных действий программа готова к анализу в ITC.
6.7.4. Создание проекта в Intel Thread Checker
- Запустите Intel® Thread Checker. Найти его можно, например, по следующему пути: Start→All programs>Intel(R) Software Development Tools→Intel(R) Thread Checker 3.0→Intel(R) Thread Checker.
- В открывшемся окне нажмите на кнопку New Project
- В окне создания проекта выберите Intel® Thread Checker Wizard и нажмите кнопку OK.
- В окне мастера в поле Launch an application укажите путь к исполняемому файлу C:\ITCLabs\DataRaces\Debug\DataRaces.exe
- Нажмите кнопку Finish.
После этого запустится ITC, произведет инструментацию программы и начнет анализ.
6.7.5. Анализ собранной информации
По окончании процесса сбора информации ITC представит результаты в виде, показанном на рис. 6.14. Итак, мы видим, что ITC нашел в предложенном коде 3 ошибки. При ближайшем рассмотрении видно, что все они указывают на одну и ту же переменную globalX. Краткие комментарии к диагностикам показывают, что ITC указал все возможные комбинации неверного обращения к переменной globalX:
- когда первый поток записывает новое значение в переменную globalX, а второй в это время читает из нее;
- когда первый поток читает из переменной globalX, а второй в это время в нее пишет;
- когда оба потока одновременно пишут в переменную globalX.
Несмотря на то, что реально работало 4 потока, очевидно, что для демонстрации ошибки достаточно двух. Именно так ITC всегда и комментирует гонки данных.
При наличии отладочной информации ITC может показать в исходном коде местоположение ошибки. Выберите любую из найденных ошибок и двойным щелчком по ней перейдите к просмотру исходного кода (рис. 6.15).
Комментарий в красном поле над исходными текстами описывает ситуацию. В представленном на рисунке случае имеет место конфликт доступа типа "запись-чтение".
6.7.6. Причина гонки данных и ее устранение
Осталась самая малость – понять, в чем причина гонки данных в рассматриваемом примере, и устранить ее. Сделать это на самом деле не так сложно. Проти- воречие с тем фактом, что обращение потоков к переменной globalX происходит внутри критической секции, а гонка данных все равно имеется, кажущееся. Проблема в данном случае не в критической секции, а в объекте, на котором она осно- вана (CRITICAL_SECTION cs). Этот объект объявлен внутри потоковой функции, а значит, является локальным для каждого потока. То есть когда один поток захватывает критическую секцию, он делает это для своего локального объекта cs, к которому остальные потоки не имеют доступа.
Исправить ситуацию можно несколькими способами, но в любом из них объявление объекта cs нужно вынести из потоковой функции increment, а инициализацию секции и освобождение ресурсов поместить в функцию main.
Возможный корректный вариант представлен ниже.
#include <stdio.h> #include <windows.h> #define NTHREADS 4 int globalX = 0; CRITICAL_SECTION cs; DWORD WINAPI increment (void *arg) { EnterCriticalSection (&cs); globalX++; LeaveCriticalSection (&cs); return 0; } int main (int argc, char *argv[]) { HANDLE h[NTHREADS]; DWORD rc; int i; printf ("START\n"); InitializeCriticalSection (&cs); for (i = 0; i < NTHREADS; i++) { h[i] = CreateThread (0, 0, increment, NULL, 0, NULL); } rc = WaitForMultipleObjects (NTHREADS, h, TRUE, INFINITE); DeleteCriticalSection (&cs); printf ("TOTAL = %d\n", globalX); printf ("STOP\n"); }
6.8. Литература
Использованные источники
- Intel® Thread Checker for Windows*. Getting Started Guide. Version 3.0. — Intel Corporation, 2006.
- Intel® Thread Checker Help. Version 3.0. — Intel Corporation, 2006.
- Intel® Thread Checker. Guide to Sample Code. Version 3.0. — Intel Corporation, 2006.
Рекомендуемая литература
- Эндрюс Г.Р. Основы многопоточного, параллельного и распределенного программирования. – М.: Издательский дом "Вильямс", 2003 (Andrews G.R. Foundations of Multithreaded, Parallel, and Distributed Programming. – Reading, MA: Addison-Wesley, 2000).
- Quinn M.J. Parallel Programming in C with MPI and OpenMP. – New York, NY: McGraw-Hill, 2004.
Курс разработан и модернизирован в лаборатории "Информационные технологии" (ITLab) факультета Вычислительной математики и кибернетики Нижегородского государственного университета им. Н.И. Лобачевского с использованием материалов, подготовленных в рамках Приоритетного национального проекта "Образование", в рамках программы развития ННГУ как Национального исследовательского университета, а также при поддержке компании Интел.