Представьте результаты исследования в виде таблиц и графиков.
5. Параллельные вычисления
По умолчанию в любом процессе есть один главный поток. Дополнительные потоки программист создаёт «вручную».
Чтобы создать новый поток, используем функцию
CreateThread.
Это готовый, системный, библиотечный вызов операционной системы. Мы используем параметры по умолчанию. Нам понадобится указать только имя подпрограммы, которая будет выполняться в новом потоке (рис. 5.1). В нашем примере это Thread2.
Рис. 5.1. Создание потока
Задание. Создайте и запустите программу по созданию второго потока. Запустите Диспетчерзадач и убедитесь, что в ней присутствует два потока.
Задание. Закомментируйте строку с бесконечным циклом while (1)и запустите программу на выполнение. Запустите Диспетчер задачи обратите внимание на количество потоков.
5.2. Привязка процесса к ядрам
На многоядерном процессоре можно назначить определённые ядра (виртуальные процессоры) для выполнения выбранного процесса. Для этого в Диспетчере задач нужно выбрать нужный процесс, вызвать контекстное меню правой кнопкой мыши и выбрать пункт Задать соответствие( Set Affinity).
В разных версиях Windows для задания соответствия используют разные вкладки Диспетчере задач:
— Windows 7 — вкладка Процессы;
— Windows 10 — вкладка Details.
В наших экспериментах можно вставить в начале программы ожидание ввода символа с клавиатуры:
getchar ()или __getch ().
После запуска программы она будет ожидать ввода. В этот момент мы изменяем привязку к ядрам. Затем нажимаем Enterна клавиатуре. Далее наша программа будет выполняться на указанных ядрах.
Задание. Запустите Диспетчер задачи обратите внимание на загрузку ядер. Задайте привязку своего процесса к одному ядру или нескольким ядрам. Обратите внимание на изменение загрузки ядер.
5.3. Параллельные потоки и ситуация гонки
Далее мы создадим программу параллельного суммирования. Для знакомства с проблемами распараллеливания будем находить сумму выбранного количества единиц. Затем задача будет постепенно усложняться.
Параллельное обращение к общей ячейке памяти (глобальной переменной) может приводить к нарушению целостности данных. Один поток переписывает содержимое ячейки, не дожидаясь завершения операции другим потоком, то есть фактически «затирает» чужие результаты. Это так называемые «гонки», или соревнование потоков за доступ к данным. Английское название Data Race.
Мы будем изучать ситуацию «гонки» на примере параллельного суммирования (рис. 5.2). Каждый поток в цикле прибавляет единицы к глобальной переменной S. Мы объявляем её до начала модулей программы. Поэтому любой поток может прочитать и записать переменную S.
Если запустить такую программу несколько раз, результаты работы будут непредсказуемыми, случайными.
В текст программы нужно добавить определение времени расчётов.
Рис. 5.2. Параллельное суммирование
Задание. Составьте программу параллельного суммирования (рис. 5.2) и запустите её на выполнение. Откройте Диспетчер задачи обратите внимание на количество потоков в процессе, а также на уровень загрузки виртуальных процессоров. Запустите программу 5 раз и запишите в отчёт продолжительность работы программы, значение суммы и ошибки.
Чтобы не допустить ситуацию гонки, используется синхронизация параллельных вычислений с помощью критической секции. Английское название — critical section.
Критическая секция — это часть параллельной программы, которую можно выполнять только последовательно, в одном потоке. Остальные потоки должны дождаться завершения работы в этой частью кода, чтобы выполнить свою работу.
Вот этапы работы с критической секцией (К.С.):
Этап 1. Объявление переменной.
До начала основного модуля программы объявляем глобальную переменную:
CRITICAL_SECTION CS.
Это своего рода «указатель» на объект типа К.С.
Этап 2. Инициализация К. С.
В основном модуле программы инициализируем критическую секцию:
InitializeCriticalSection (&CS).
Начиная с этого момента, мы можем её использовать.
Этап 3. Начало К. С.
Читать дальше
Конец ознакомительного отрывка
Купить книгу