Опубликован: 02.10.2012 | Уровень: специалист | Доступ: платный | ВУЗ: Нижегородский государственный университет им. Н.И.Лобачевского
Лекция 6:

Введение в использование инструментов для параллельного программирования на примере пакета 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.

Открытие файла DataRaces.с

Рис. 6.4. Открытие файла DataRaces.с

Интерес в этом небольшом файле представляет потоковая функция 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.
Результаты работы примера DataRaces (исходный вариант)

Рис. 6.5. Результаты работы примера DataRaces (исходный вариант)

Кажется, все верно. Число потоков равно четырем. Начальное значение переменной 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.

Результаты работы примера DataRaces (версия с циклом)

Рис. 6.6. Результаты работы примера DataRaces (версия с циклом)

Не правда ли, неожиданно?! Вместо 40000 на экране совсем другое число! Повторите запуск несколько раз и убедитесь, что результат работы программы будет меняться. Типичная ситуация для гонки данных.

Итак, приложив некоторые усилия, мы прошли два этапа в рассмотренном в п. 1.1 процессе отладки. Наличие ошибки обнаружено, локализация ее также не вызывает сложностей в силу малого размера кода в примере. Осталось понять, в чем же причина ошибки. Но прежде посмотрим, как с ее обнаружением справится ITC.

6.7.3. Подготовка программы для анализа

Выполните следующие действия для подготовки программы к анализу.

  1. Верните код примера к исходному состоянию.
  2. Конвертируйте проект для использования компилятора Intel® C++ Compiler. В окне Solution Explorer выберите файл проекта, щелкните правой кнопкой мыши и в контекстном меню выполните команду Convert to use Intel® C++ Project System.
  3. В меню Project выберите пункт Properties, в появившемся окне настроек проекта в дереве слева выберите узел Configuration Properties →C/C++ →General (рис. 6.7). В открывшейся таблице справа убедитесь, что значение поля Debug Information Format равно Program Database (/Zi).
    Указание формата отладочной информации

    Рис. 6.7. Указание формата отладочной информации
  4. В дереве слева выберите узел Configuration Properties→C/C++→Optimization (рис. 6.8). В открывшейся таблице справа убедитесь, что значение поля Optimization равно Disabled(/Od).
    Отключение оптимизации

    Рис. 6.8. Отключение оптимизации
  5. В дереве слева выберите узел Configuration Properties→C/C++→Code Generation (рис. 6.9). В открывшейся таблице справа убедитесь, что значение поля Runtime Library установлено в Multi-threaded Debug DLL (/MDd).
    Выбор потокобезопасных библиотек

    Рис. 6.9. Выбор потокобезопасных библиотек
  6. В дереве слева выберите узел Configuration Properties→Linker→Command Line (рис. 6.10). Убедитесь, что сборка программы выполняется с использованием опции компоновщика /FIXED:NO.
    Установка опций компоновщика

    Рис. 6.10. Установка опций компоновщика
  7. Убедитесь, что включена компиляторная инструментация кода. В дереве слева выберите узел Configuration Properties→C/C++→Command Line (рис. 6.11). Убедитесь, что в поле Additional Options установлен ключ компилятора /Qtcheck
    Установка компиляторного режима инструментации

    Рис. 6.11. Установка компиляторного режима инструментации

После выполнения указанных действий программа готова к анализу в ITC.

6.7.4. Создание проекта в Intel Thread Checker
  1. Запустите Intel® Thread Checker. Найти его можно, например, по следующему пути: Start→All programs>Intel(R) Software Development Tools→Intel(R) Thread Checker 3.0→Intel(R) Thread Checker.
  2. В открывшемся окне нажмите на кнопку New Project  New Project
  3. В окне создания проекта выберите Intel® Thread Checker Wizard и нажмите кнопку OK.
    Выбор типа проекта

    Рис. 6.12. Выбор типа проекта
  4. В окне мастера в поле Launch an application укажите путь к исполняемому файлу C:\ITCLabs\DataRaces\Debug\DataRaces.exe
    Выбор программы для анализа

    Рис. 6.13. Выбор программы для анализа
  5. Нажмите кнопку Finish.

После этого запустится ITC, произведет инструментацию программы и начнет анализ.

6.7.5. Анализ собранной информации

По окончании процесса сбора информации ITC представит результаты в виде, показанном на рис. 6.14. Итак, мы видим, что ITC нашел в предложенном коде 3 ошибки. При ближайшем рассмотрении видно, что все они указывают на одну и ту же переменную globalX. Краткие комментарии к диагностикам показывают, что ITC указал все возможные комбинации неверного обращения к переменной globalX:

  • когда первый поток записывает новое значение в переменную globalX, а второй в это время читает из нее;
  • когда первый поток читает из переменной globalX, а второй в это время в нее пишет;
  • когда оба потока одновременно пишут в переменную globalX.

Несмотря на то, что реально работало 4 потока, очевидно, что для демонстрации ошибки достаточно двух. Именно так ITC всегда и комментирует гонки данных.

Результат анализа примера DataRaces – Diagnostics

Рис. 6.14. Результат анализа примера DataRaces – Diagnostics

При наличии отладочной информации ITC может показать в исходном коде местоположение ошибки. Выберите любую из найденных ошибок и двойным щелчком по ней перейдите к просмотру исходного кода (рис. 6.15).

 Результат анализа примера DataRaces – Source View

Рис. 6.15. Результат анализа примера DataRaces – Source View

Комментарий в красном поле над исходными текстами описывает ситуацию. В представленном на рисунке случае имеет место конфликт доступа типа "запись-чтение".

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. Литература

Использованные источники

  1. Intel® Thread Checker for Windows*. Getting Started Guide. Version 3.0. — Intel Corporation, 2006.
  2. Intel® Thread Checker Help. Version 3.0. — Intel Corporation, 2006.
  3. Intel® Thread Checker. Guide to Sample Code. Version 3.0. — Intel Corporation, 2006.

Рекомендуемая литература

  1. Эндрюс Г.Р. Основы многопоточного, параллельного и распределенного программирования. – М.: Издательский дом "Вильямс", 2003 (Andrews G.R. Foundations of Multithreaded, Parallel, and Distributed Programming. – Reading, MA: Addison-Wesley, 2000).
  2. Quinn M.J. Parallel Programming in C with MPI and OpenMP. – New York, NY: McGraw-Hill, 2004.

Курс разработан и модернизирован в лаборатории "Информационные технологии" (ITLab) факультета Вычислительной математики и кибернетики Нижегородского государственного университета им. Н.И. Лобачевского с использованием материалов, подготовленных в рамках Приоритетного национального проекта "Образование", в рамках программы развития ННГУ как Национального исследовательского университета, а также при поддержке компании Интел.

Дмитрий Остапенко
Дмитрий Остапенко

поддерживаю выше заданые вопросы

 

Павел Каширин
Павел Каширин

Скачал архив и незнаю как ничать изучать материал. Видео не воспроизводится (скачено очень много кодеков, различных плееров -- никакого эффекта. Максимум видно часть изображения без звука). При старте ReplayMeeting и Start в браузерах google chrome, ie возникает script error с невнятным описанием. В firefox ситуация еще интереснее. Выводится: 

Meet Now: Кукаева Светлана Александровна. 

Meeting Start Time: 09.10.2012, 16:58:04
Meeting Stop Time: 09.10.2012, 18:45:18
Recording Duration:01:47:14

Downloading...

Your Web browser is not configured to play Windows Media audio/video files.

Make sure the features are enabled and available.

 

Анита Васильева
Анита Васильева
Россия, Санкт-Петербург, Санкт-Петербургский государственный университет
Светлана Кукаева
Светлана Кукаева
Россия