Опубликован: 28.10.2009 | Уровень: специалист | Доступ: платный
Лекция 6:

Отладка параллельной программы

9.7. Пример использования Intel Thread Checker

Для начального знакомства с ITC рассмотрим пример, входящий в поставку инструмента версии 3.0.

9.7.1. Описание примера

Наличие у каждого потока в многопоточном приложении доступа к общему ВАП процесса позволяет потокам эффективно обмениваться данными, с одной стороны, и является основным источником ошибок, с другой. Гонки данных, они же конфликты доступа ( storage conflicts ), поджидают зазевавшегося программиста буквально на каждом шагу. Поиск таких ошибок методом "пристального взгляда" - задача весьма сложная.

Рассматриваемый пример создает 4 потока, каждый из которых увеличивает значение общей глобальной переменной globalX и использует критическую секцию для синхронизации доступа к ней. Однако, несмотря на наличие критической секции, в коде все-таки содержится конфликт доступа. Нам предстоит его обнаружить, выяснить причину и исправить ситуацию.

9.7.2. Изучение примера

Откройте проект DataRaces, последовательно выполняя следующие шаги:

  • запустите приложение Microsoft Visual Studio 2005,
  • в меню File выполните команду Open \to Project/Solution…,
  • в диалоговом окне Open Project выберите папку C:\ITCLabs\DataRaces,
  • дважды щелкните на файле DataRaces.dsw или, выбрав файл, выполните команду Open ;
  • согласитесь с предложением конвертировать проект DataRaces.dsp в новый формат.

После открытия проекта в окне Solution Explorer дважды щелкните на файле исходного кода DataRaces.с, как это показано на рис. 9.4. После этих действий программный код, с которым предстоит работать, будет открыт в редакторе кода Microsoft Visual Studio 2005.

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

Рис. 9.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.

Убедитесь, что вывод на экран соответствует представленному на рис. 9.5.

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

увеличить изображение
Рис. 9.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;
}

Добавьте в код выделенные в предыдущем фрагменте строки, соберите и запустите пример. Убедитесь, что вывод на экран соответствует представленному на рис. 9.6.

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

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

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

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

Алексей Николаев
Алексей Николаев
Россия, г. Саранск
Рамиль Ариков
Рамиль Ариков
Россия, Республика Мордовия