Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Параллельное программирование над общей памятью. POSIX Threads. Учебное пособие

.pdf
Скачиваний:
0
Добавлен:
12.08.2026
Размер:
1 Мб
Скачать

}

void *consumer(void *arg)

{

int err;

//

while(true) {

//Захватываем мьютекс и ожидаем появления товаров на складе err = pthread_mutex_lock(&mutex);

if(err != 0)

err_exit(err, "Cannot lock mutex"); while(state == EMPTY) {

err = pthread_cond_wait(&cond, &mutex); if(err != 0)

err_exit(err, "Cannot wait on condition variable");

}

//Получен сигнал, что на складе имеется товар.

//Потребляем его.

cout << "Consuming number " << store << "..."; sleep(1);

cout << "done" << endl; state = EMPTY;

// Посылаем сигнал, что на складе не осталось товаров. err = pthread_cond_signal(&cond);

if(err != 0)

err_exit(err, "Cannot send signal"); err = pthread_mutex_unlock(&mutex); if(err != 0)

err_exit(err, "Cannot unlock mutex");

}

}

int main()

{

int err;

//

pthread_t thread1, thread2; // Идентификаторы потоков

// Инициализируем мьютекс и условную переменную err = pthread_cond_init(&cond, NULL);

41

if(err != 0)

err_exit(err, "Cannot initialize condition variable"); err = pthread_mutex_init(&mutex, NULL);

if(err != 0)

err_exit(err, "Cannot initialize mutex"); // Создаём потоки

err = pthread_create(&thread1, NULL, producer, NULL); if(err != 0)

err_exit(err, "Cannot create thread 1");

err = pthread_create(&thread2, NULL, consumer, NULL); if(err != 0)

err_exit(err, "Cannot create thread 2");

//Дожидаемся завершения потоков pthread_join(thread1, NULL); pthread_join(thread2, NULL);

//Освобождаем ресурсы, связанные с мьютексом

//и условной переменной

pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond);

}

ДОПОЛНИТЕЛЬНАЯ ЛИТЕРАТУРА

1. Средства параллельного программирования в ОС LINUX: учебное пособие / Р.Х. Садыхов, Л.П. Поденок, А.В. Отвагин и др. [Электронный ресурс] // OpenNET. – URL: https://www.opennet.ru/docs/RUS/linux_parallel/ (дата обращения: 06.11.2017).

2. Blaise Barney. POSIX Threads Programming [Electronic resource] // Lawrence Livermore National Laboratory. – URL: https://computing.llnl.gov/tutorials/pthreads/ (accessed: 28.07.2017).

3. Многопоточное программирование с использованием POSIX Threads [Электронный ресурс] // Национальный открытый университет «ИНТУИТ». – URL: http://www.intuit.ru/studies/professional_skill_improvements/16994/info (дата обращения: 04.08.2017).

4. Пазников А.А. Лекция 2. POSIX Threads. Жизненный цикл потоков. Планирование. Критические секции. Синхронизация [Электронный ресурс] // Центр параллельных вычислительных технологий СибГУТИ. – URL: http://cpct.sibsutis.ru/~apaznikov/teaching/uploads/Site/pct-spring2015-lec2.pdf (дата обращения: 06.11.2017).

42

3. ПЛАНИРОВАНИЕ ПОТОКОВ

МОДЕЛЬ ПЛАНИРОВАНИЯ ПОТОКОВ

Поток может находиться в нескольких состояниях:

активный (англ. running) – выполняется на процессоре;

готовый к запуску (англ. ready) – может и хочет запуститься, но пока для него нет ресурсов;

спящий (англ. blocked) – не готов к запуску, так как ожидает события, например, изменения состояния переменной синхронизации, сигнала от условной переменной или завершения операции ввода / вывода;

завершенный – выполнение потока закончено, но ресурсы еще не освобождены.

Возможные переходы между состояниями показаны на рис. 2. Основная задача планировщика потоков заключается в периодическом переводе потоков из состояния «готовый к запуску» в состояние «активный» (с соответствующим выделением необходимых ресурсов) и обратно согласно заданной политике планирования и набора определенных приоритетов.

Рис. 2. Переходы между состояниями потока

43

Модель планирования выглядит следующим образом. Имеется упорядоченный список потоков для каждого значения приоритета. Все готовые к запуску потоки должны быть включены в соответствующий список согласно своему приоритету. На исполнение, независимо от политики планирования, всегда запускается поток, являющийся первым элементом в списке с максимальным приоритетом. Политика планирования определяет, как долго поток может исполняться, а также в какой список потоков и в какую его часть он будет включен в случае приостановки исполнения.

ПОЛИТИКИ ПЛАНИРОВАНИЯ

Основные типы политик планирования приведены в табл. 5. Конкретная реализация может добавлять дополнительные политики планирования.

Политика

планирования

SCHED_FIFO

SCHED_RR

Таблица 5

Политики планирования

Описание

Поток исполняется до тех пор, пока не перейдет в состояние «спящий» или добровольно не отдаст управление. Когда «активный поток» переходит в состояние «готовый к запуску» (вытесняется планировщиком ОС), он попадает в начало списка потоков своего приоритета. Когда поток переходит из состояния «спящий» в состояние «готовый к запуску», он попадает в конец списка потоков своего приоритета. Если поток добровольно отдает управление, он попадает в конец списка потоков своего приоритета

Поток исполняется до тех пор, пока не перейдет в состояние «спящий» или добровольно не отдаст управление, либо пока не исчерпается отведенный ему квант времени. Когда активный поток вытесняется планировщиком ОС, он попадает в начало списка потоков своего приоритета. Когда поток переходит из состояния «спящий» в состояние «готовый к запуску», он попадает в конец списка потоков своего приоритета. Если поток добровольно отдает управление либо исчерпывает отведенный ему квант времени (согласно политике планирования), он попадает в конец списка потоков своего приоритета, при этом на исполнение запускается следующий поток, являющийся первым в списке

44

Политика

планирования

SCHED_OTHER

Окончание табл. 5

Описание

Политика планирования, определяемая реализацией Pthreads. В библиотеке libpthread – это планирование

с разделением времени и использованием динамического приоритета: значение приоритета повышается каждый раз, когда поток может запуститься, но откладывается планировщиком

Изменить политику планирования и приоритет потока можно через его атрибуты schedpolicy и schedparam, установка значений кото-

рых осуществляется функциями pthread_attr_setschedpolicy() и pthread_attr_setschedparam() соответственно.

Прототипы функций:

int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);

int pthread_attr_getschedpolicy(const pthread_attr_t *restrict attr, int *restrict policy);

int pthread_attr_setschedparam(pthread_attr_t *restrict attr, const struct sched_param *restrict param);

int pthread_attr_getschedparam(const pthread_attr_t *restrict attr, struct sched_param *restrict param);

int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);

int pthread_attr_getinheritsched(const pthread_attr_t *restrict attr, int *restrict inheritsched);

Параметры функций:

attr – указатель на атрибуты потока;

policy – политика планирования; перечень возможных значений приведен в табл. 5;

param – указатель на переменную (структуру типа sched_param), содержащую параметры планирования; обязательным элементом структуры является sched_priority – параметр, определяющий приоритет потока; конкретные реализации Pthreads могут добавлять дополнительные параметры;

45

inheritsched – определяет, будет ли создаваемый поток наследовать политику планирования и ее атрибуты от родителя (значение атрибута PTHREAD_INHERIT_SCHED) или получит их из атрибутов policy и param (значение атрибута PTHREAD_EXPLICIT_SCHED).

Сообщения об ошибках:

ноль – ошибки отсутствуют; pthread_attr_setschedpolicy():

ENOTSUP – попытка установить неподдерживаемое значение атрибута.

pthread_attr_setschedparam():

ENOTSUP – попытка установить неподдерживаемое значение атрибута.

pthread_attr_setinheritsched():

ENOTSUP – попытка установить неподдерживаемое значение атрибута.

РАСПРЕДЕЛЕНИЕ ПРОЦЕССОРНОГО ВРЕМЕНИ

Еще один параметр, влияющий на планирование потоков, определяет способ распределения процессорного времени между потоками. Различают два способа выделения потоку процессорного времени:

PTHREAD_SCOPE_SYSTEM – поток конкурирует за ресурсы процессора на уровне ядра наряду с другими процессами системы;

PTHREAD_SCOPE_PROCESS – поток конкурирует за ресурсы процессора на уровне процесса, которому он принадлежит. В этом случае, как правило, операционная система выбирает процесс, который запускается на исполнение, а некоторый внутренний планировщик процесса в соответствии с политиками Pthreads выбирает поток для исполнения.

Установить способ выделения процессорного времени потоку

можно с помощью функции pthread_attr_setscope().

Прототипы функций:

int pthread_attr_setscope(pthread_attr_t *attr, int contentionscope);

int pthread_attr_getscope(const pthread_attr_t *restrict attr, int *restrict contentionscope);

46

Параметры функций:

attr – указатель на атрибуты потока;

contentionscope – атрибут, указывающий на способ выделения процессорного времени потоку.

Сообщения об ошибках:

ноль – ошибки отсутствуют; pthread_attr_setscope():

ENOTSUP – попытка установить неподдерживаемое значение атрибута.

ДОПОЛНИТЕЛЬНАЯ ЛИТЕРАТУРА

1.Frank Singhoff. Programming Real-Time Embedded systems : C/POSIX and RTEMS [Electronic resource] // University of Brest, Faculty of Sciences. – URL: http://beru.univ-brest.fr/~singhoff/ENS/USTH/posix.pdf (accessed: 07.11.2017).

2.Giorgio Buttazzo and Giuseppe Lipari. Ptask: an Educational C Library for Programming Real-Time Systems on Linux [Electronic resource] // SemanticScholar. – URL: https://pdfs.semanticscholar.org/66f9/24f92a4a323cdc7d3aaa926848c

56d509acc.pdf (accessed: 07.11.2017).

3. NPTL Optimization for Lightweight Embedded Devices [Electronic resource] // The Linux Kernel Archives. – URL: https://www.kernel.org/ doc/ols/2011/ols2011-lim.pdf (accessed: 07.11.2017).

47

4. ОТЛАДКА МНОГОПОТОЧНЫХ ПРОГРАММ

ТИПОВЫЕ ОШИБКИ МНОГОПОТОЧНЫХ ПРОГРАММ

Отладка программ – это комплекс мероприятий, направленный на поиск и устранение ошибок в программном коде. В отличие от последовательного программирования отладка многопоточных программ зачастую осложняется недетерминизмом их исполнения: порядок исполнения инструкций разными потоками по отношению друг к другу часто не фиксирован и может изменяться от запуска к запуску. Вследствие этого ошибки взаимодействия потоков могут проявляться не при каждом запуске и, следовательно, быть трудными для обнаружения, воспроизведения и исправления.

К типичным ошибкам, встречающимся в многопоточных программах, можно отнести следующие.

Гонки данных (англ. data races) – незащищенный (несинхронизированный) доступ из нескольких потоков к общей переменной. Ошибка происходит, если более одного потока производят запись. Одновременное чтение несколькими потоками одной переменной без ее модификации гонкой данных не является и не приводит к ошибке. При наличии гонки данных результат выполнения программы зависит от порядка обращения потоков к переменной. Этот порядок может быть разным при каждом запуске программы, что усложняет отладку.

Дедлок (англ. deadlock) – состояние, когда несколько потоков находятся в режиме ожидания некоторых ресурсов, но требуемые ресурсы никогда не будут освобождены. Пример: пусть каждому из двух потоков для продолжения исполнения необходимо захватить два мьютекса. Если каждый поток захватывает мьютексы поодиночке и порядок их захвата различен, возможна ситуация, когда каждый поток захватит по одному мьютексу. В этом случае ни один поток не может захватить второй мьютекс (так как он уже захвачен другим потоком) и потоки будут ожидать бесконечно.

48

Лайвлок (англ. livelock) – состояние, когда исполнение потоков заключается в периодической безуспешной попытке получить доступ к некоторым ресурсам. Хотя потоки не находятся в бесконечном ожидании, полезных вычислений не производится. Пример: потоки из предыдущего примера могут при обнаружении дедлока попытаться освободить занятый мьютекс и попробовать захватить оба мьютекса снова; возможна ситуация, когда мьютексы всегда будут освобождаться и захватываться в одном и том же порядке, т. е. снова и снова будет возникать дедлок.

Голодание (англ. starvation) – состояние, когда один или несколько потоков никак не могут получить нужные ресурсы для исполнения (другие потоки при этом могут исполняться нормально). Пример: при примитивной или некорректной политике выделения процессорного времени могут найтись потоки, никогда не получающие процессорное время и, следовательно, не исполняющиеся, т. е. находящиеся в состоянии голодания.

ОСОБЕННОСТИ ИСПОЛЬЗОВАНИЯ POSIX THREADS

При программировании с использованием Pthreads возникают специфические ошибки, связанные с технической реализацией этого интерфейса. К наиболее частым из них относятся:

отсутствие вызова функции освобождения захваченного мьютекса (может привести к дедлоку) либо освобождение незахваченного мьютекса (некорректное использование API);

использование мьютекса как условной переменной (вместо использования в паре с условной переменной);

использование условной переменной без соответствующего мьютекса;

отсутствие обработки ложного пробуждения потока при ожидании на условной переменной;

отсутствие ожидания завершения порожденных потоков или преждевременное завершение дочерних потоков (может привести к порче общих данных и утечкам памяти);

создание очень большого количества потоков с размером стека «по умолчанию» (может привести к нехватке памяти).

49

ИНСТРУМЕНТЫ ОТЛАДКИ МНОГОПОТОЧНЫХ ПРОГРАММ

Для ручной отладки параллельных программ используются специализированные отладчики (англ. debugger), позволяющие выполнить пошаговый анализ исполнения параллельной программы.

Для автоматической отладки параллельных программ используются различные инструментальные средства, позволяющие автоматически обнаружить явные и неявные ошибки в коде. Их можно разбить на три основные категории.

1.Формальная верификация. Параллельная программа, или система, представляется в виде некоторой модели, которая затем анализируется на наличие или отсутствие нужных свойств (например, отсутствие дедлоков или гонок данных) формальными методами, такими как дедуктивный анализ, проверка моделей, или абстрактная интерпретация.

Сточки зрения автоматизации наиболее развит метод проверки моделей (англ. model checking). Для получения модели из исходной программы используется спецификация программы на специализированном языке. Например, это можно сделать с помощью метаязыка PROMELA для анализатора SPIN. Отдельные решения имеются для моделей на базе сетей Петри: c помощью инструментов C2Petri или Evinrude можно сгенерировать модель на основе исходного кода программы.

2.Статический анализ кода. Производится без непосредственного исполнения самой программы. Анализируется либо исходный, либо преобразованный объектный код программы. Цель анализа – обнаружение участков программы, содержащих потенциальные уязвимости, ошибки, нарушения кросс-платформенности и т. д. Проверяется только возможность наличия ошибки, при этом нет 100-процентной гарантии, что ошибка действительно есть. Нередко статические анализаторы выдают значительное число ложных срабатываний (т. е. указания на ошибки, которых на самом деле нет). В качестве примеров статических анализаторов можно привести Lint, PVS-Studio.

3.Динамический анализ кода. Выполняется путем запуска программы на реальном или виртуальном процессоре и отслеживания и анализа характеристик ее исполнения. Для проведения динамического анализа может потребоваться перекомпиляция программы, загрузка специальных библиотек, встраивание в код средств инструментации. Динамические анализаторы проверяют только исполненные участки кода, соответственно могут быть обнаружены не все ошибки, или не-

50

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]