Россия, г. Саранск |
Отладка параллельной программы
9.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, произведет инструментацию программы и начнет анализ.
9.7.5. Анализ собранной информации
По окончании процесса сбора информации ITC представит результаты в виде, показанном на рис. 9.14. Итак, мы видим, что ITC нашел в предложенном коде 3 ошибки. При ближайшем рассмотрении видно, что все они указывают на одну и ту же переменную globalX. Краткие комментарии к диагностикам показывают, что ITC указал все возможные комбинации неверного обращения к переменной globalX:
- когда первый поток записывает новое значение в переменную globalX, а второй в это время читает из нее;
- когда первый поток читает из переменной globalX,а второй в это время в нее пишет;
- когда оба потока одновременно пишут в переменную globalX.
Несмотря на то, что реально работало 4 потока, очевидно, что для демонстрации ошибки достаточно двух. Именно так ITC всегда и комментирует гонки данных.
При наличии отладочной информации ITC может показать в исходном коде местоположение ошибки. Выберите любую из найденных ошибок и двойным щелчком по ней перейдите к просмотру исходного кода (рис. 9.15).
Комментарий в красном поле над исходными текстами описывает ситуацию. В представленном на рисунке случае имеет место конфликт доступа типа "запись-чтение".
9.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"); }
9.8 Заключение
Ни для кого не секрет, наличие инструментов делает жизнь проще. Перефразируем: наличие Intel® Thread Checker существенно скрашивает суровые будни разработчика многопоточных программ. Убедиться в этом еще раз читатель сможет в лабораторной работе "Отладка параллельной программы с использованием Intel Thread Checker", в которой приводится дополнительная информация по ITC.