Файл: Б. М. Глинский канд техн наук, доцент.pdf

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 23.11.2023

Просмотров: 117

Скачиваний: 2

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

12
Пример 1. Создание потока
#include
#include
#include
#include using namespace std;
/* Функция, которую будет исполнять созданный поток */ void *thread_job(void *arg)
{ cout << "Thread is running..." << endl;
} int main()
{
// Определяем переменные: идентификатор потока и код ошибки pthread_t thread; int err;
// Создаём поток err = pthread_create(&thread, NULL, thread_job, NULL);
// Если при создании потока произошла ошибка, выводим
// сообщение об ошибке и прекращаем работу программы if(err != 0) { cout << "Cannot create a thread: " << strerror(err) << endl; exit(-1);
}
// Ожидаем завершения созданного потока перед завершением
// работы программы pthread_exit(NULL);
}
А
ТРИБУТЫ ПОТОКОВ
Атрибуты потока позволяют менять его свойства. Они опреде- ляются только во время создания потока и не могут быть изменены позднее.

13
При необходимости переопределить атрибуты потока, исполь- зуемые по умолчанию, необходимо объявить переменную типа pthread_attr_t, которая будет содержать сведения о значениях атрибутов потока, инициализировать ее с помощью функции pthread_attr_ init(), изменить значения необходимых атрибутов с помощью соответствующих функций и передать ее в качестве пара- метра в функцию pthread_create(). Одну и ту же переменную можно использовать для создания нескольких потоков. При этом, если изменить переменную (изменить значение одного из атрибутов) после создания потока, на нем это никак не отразится. Когда переменная больше не нужна (например, все необходимые потоки созданы, либо программа завершается), связанную с ней информацию необходимо удалить с помощью функции pthread_attr_destroy().
Прототипы функций: int pthread_attr_init(pthread_attr_t *attr); int pthread_attr_destroy(pthread_attr_t *attr);
Параметры функций:
 attr
– указатель на атрибуты потока.
Сообщения об ошибках:
 ноль – ошибки отсутствуют. pthread_attr_init():
 ENOMEM – при инициализации нового объекта attr не хватило па- мяти.
С каждым атрибутом связаны две функции: одна позволяет изме- нять значения атрибута, а вторая – прочитать его значение. Основные функции
2
для работы с атрибутами:
Прототипы функций: int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
2
Дополнительные атрибуты, относящиеся к планированию потоков, и функции для работы с ними рассмотрены в разделе 3.

14 int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize); int pthread_attr_getguardsize(const pthread_attr_t *restrict attr, size_t *restrict guardsize); int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize); int pthread_attr_getstack(const pthread_attr_t *restrict attr, void **restrict stackaddr, size_t *restrict stacksize); int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize); int pthread_attr_getstacksize(const pthread_attr_t *restrict attr, size_t *restrict stacksize);
Параметры функций:
 attr – указатель на атрибуты потока;
 остальные параметры задают значения атрибутов и описаны в табл. 2.
Т а б л и ц а 2

Перечень атрибутов потока
Атрибут
Возможные значения
Описание detachstate PTHREAD_CREATE_DETACHED
PTHREAD_CREATE_JOINABLE
Определяет тип создавае- мого потока: присоединя- емый или отсоединенный
(см. ниже) guardsize
Размер в байтах
Задает размер охранной зоны для защиты от пере- полнения стека (выделяет- ся в памяти сразу за сте- ком) stackaddr
Адрес в байтах
Задает адрес стека stacksize
Размер в байтах
Задает размер стека

15
Сообщения об ошибках:
 ноль – ошибки отсутствуют;
 EINVAL – неверное значение атрибута.
Программа из примера 2 создает новый поток, который получит нестандартный размер стека.
Пример 2. Изменение атрибутов потока
#include
#include
#include
#include using namespace std;
/* Функция, которую будет исполнять созданный поток */ void *thread_job(void *arg)
{ cout << "Thread is running..." << endl;
} int main()
{
// Определяем переменные pthread_t thread; //
Идентификатор потока pthread_attr_t thread_attr;
// Атрибуты потока int err;
// Код ошибки
// Инициализируем переменную для хранения атрибутов потока err = pthread_attr_init(&thread_attr); if(err != 0) { cout << "Cannot create thread attribute: " << strerror(err) << endl; exit(-1);
}
// Устанавливаем минимальный размер стека для потока (в байтах) err = pthread_attr_setstacksize(&thread_attr, 5*1024*1024); if(err != 0) { cout << "Setting stack size attribute failed: " << strerror(err)
<< endl; exit(-1);
}

16
// Создаём поток err = pthread_create(&thread, &thread_attr, thread_job, NULL); if(err != 0) { cout << "Cannot create a thread: " << strerror(err) << endl; exit(-1);
}
// Освобождаем память, занятую под хранение атрибутов потока pthread_attr_destroy(&thread_attr);
// pthread_exit(NULL);
}
П
РИСОЕДИНЯЕМЫЕ И ОТСОЕДИНЕННЫЕ ПОТОКИ
Поток может быть создан присоединяемым и отсоединенным. Раз- личие между ними состоит в способе освобождения ресурсов (иденти- фикатор потока, регистры, стек): у отсоединенных потоков ресурсы освобождаются автоматически при завершении работы, тогда как остальные потоки требуют присоединения к одному из выполняющих- ся потоков для освобождения системных ресурсов. По своей сути при- соединение потоков является одним из видов синхронизации.
Тип создаваемого потока определяется атрибутом detachstate: если он установлен в значение PTHREAD_CREATE_DETACHED, будет создан отсоединенный поток; если же его значение равно
PTHREAD_CREATE_JOINABLE – будет создан присоединяемый поток.
По умолчанию поток создается присоединяемым.
Для присоединения потока используется функция pthread_ join(). Она приостанавливает выполнение текущего потока до мо- мента завершения присоединяемого потока, идентификатор которого указан в качестве параметра. Из присоединяемого потока имеется воз- можность получить возвращаемое значение.
Прототип функции: int pthread_join(pthread_t thread, void **value_ptr);
Параметры функции:
 thread – идентификатор присоединяемого потока;
 value_ptr – указатель на область памяти, через которую можно получить значение, переданное функции pthread_exit() при за-


17 вершении потока; допустимо указывать NULL, если это значение не нужно.
Рис. 1. Создание потоков
Сообщения об ошибках:
 ноль – ошибки отсутствуют;
 ESRCH – не удается найти поток, соответствующий идентификато- ру thread.
На рис. 1 показано 5 потоков. Функцию main() исполняет поток 1
(основной поток). Потоки 2, 3 и 4 созданы с типом PTHREAD_CREATE_
JOINABLE, а поток 5 – с типом PTHREAD_CREATE_DETACHED. Видно, что присоединять поток можно как к его родителю (в примере – пото- ки 2 и 3), так и к другому потоку (поток 4).
Функция pthread_detach() позволяет изменить тип потока, из- начально созданного как присоединяемый, на отсоединенный.
Прототип функции:
int pthread_detach(pthread_t thread);

18
Параметры функции:
 thread – идентификатор потока, тип которого необходимо изме- нить на отсоединенный.
Сообщения об ошибках:
 ноль – ошибки отсутствуют.
Отсоединенный поток по-прежнему работает в рамках своего про- цесса и в случае его завершения (прекращения работы функции main()) будет также завершен. Чтобы все отсоединенные потоки кор- ректно завершили свою работу, последней командой функции main() должен быть вызов функции pthread_exit().
П
ЕРЕДАЧА ПАРАМЕТРОВ В ПОТОКИ
При создании потока имеется возможность передать в функцию потока указатель на область, содержащую параметры. Указатель необ- ходимо привести к типу (void *) и передать последним параметром функции pthread_create().
Программа из примера 3 передает число, указанное пользователем при запуске программы, в качестве аргумента в функцию потока.
Пример 3. Передача параметра в функцию потока
#include
#include
#include
#include using namespace std; void *thread_job(void *arg)
{
// Преобразуем указатель на параметр к правильному типу int *param = (int *) arg;
// Выводим параметр на экран cout << "Thread is running... Parameter is " << *param << endl;
} int main(int argc, char *argv[])
{

19
// Определяем переменные pthread_t thread; int param; // Переменная для хранения параметра, передаваемого потоку int err;
// if(argc != 2) { cout << "Wrong number of arguments" << endl; exit(-1);
} param = atoi(argv[1]);
// Создаем поток err = pthread_create(&thread, NULL, thread_job, (void *) &param); if(err != 0) { cout << "Cannot create a thread: " << strerror(err) << endl; exit(-1);
}
// Ожидаем завершения работы потока pthread_join(thread, NULL); if(err != 0) { cout << "Cannot join a thread: " << strerror(err) << endl; exit(-1);
}
}
П
РОЧИЕ ФУНКЦИИ
Функция pthread_self() возвращает идентификатор вызываю- щего потока.
Прототип функции: pthread_t pthread_self(void);
Параметры функции:
 отсутствуют.
Сообщения об ошибках:
 отсутствуют.
Поскольку представление идентификатора потока определяется ре- ализацией (например, он может быть реализован как структура), срав- нивать идентификаторы с помощью операторов сравнения некоррект-


20 но. Для этого служит специальная функция pthread_equal()
. Она возвращает ноль, если идентификаторы разные, и число больше нуля, если идентификаторы одинаковы.
Прототип функции: int pthread_equal(pthread_t t1, pthread_t t2);
Параметры функции:
 t1 – идентификатор первого потока;
 t2 – идентификатор второго потока.
Сообщения об ошибках:
 отсутствуют.
Функция pthread_once()
позволяет выполнить некоторую функцию однократно любым потоком процесса. Вызывать ее могут все потоки. Первое обращение к функции pthread_once()
приведет к исполнению функции init_routine()
, при последующих обраще- ниях функция init_routine()
выполнена не будет.
Прототип функции:
int pthread_once(pthread_once_t *once_control, void
(*init_routine)(void));
Параметры функции:
 once_control – синхронизирующая структура данных, которая должна быть инициализирована до обращения к функции pthread_once() значением PTHREAD_ONCE_INIT;
 init_routine – функция, которая будет исполнена однократно.
Сообщения об ошибках:
 ноль – ошибки отсутствуют.
Д
ОПОЛНИТЕЛЬНАЯ ЛИТЕРАТУРА
1. Средства параллельного программирования в ОС LINUX: учебное по- собие / Р.Х. Садыхов, Л.П. Поденок, А.В. Отвагин и др. [Электронный ресурс] //
OpenNET. – URL: https://www.opennet.ru/docs/RUS/linux_parallel/ (дата обра- щения: 06.11.2017).

21 2. Blaise Barney. POSIX Threads Programming [Electronic resource] // Law- rence Livermore National Laboratory. – URL: https://computing.llnl.gov/tuto- rials/pthreads/ (accessed: 28.07.2017).
3. Многопоточное программирование с использованием POSIX Threads
[Электронный ресурс] // Национальный открытый университет «ИНТУИТ». –
URL: http://www.intuit.ru/studies/professional_skill_improvements/16994/info (да- та обращения: 04.08.2017).
4. Pthreads: Потоки в русле POSIX [Электронный ресурс] // Хабрахабр. –
URL: https://habrahabr.ru/post/326138/ (дата обращения: 08.08.2017).

22
2. СИНХРОНИЗАЦИЯ ПОТОКОВ
М
ЬЮТЕКСЫ
Потоки, принадлежащие одному процессу, имеют общее адресное пространство. Это означает, что если для процесса определены гло- бальные переменные, то любой поток может иметь к ним доступ, в том числе – изменять. Может оказаться, что один поток читает данные в то время, когда другой поток их записывает. Механизмы синхронизации позволяют исключить подобные случаи. Кроме того, они позволяют управлять порядком исполнения потоков, что может потребоваться, например, если одному потоку для продолжения работы необходимы данные от другого потока.
Pthreads предоставляет различные средства синхронизации пото- ков. К наиболее часто используемым относятся мьютексы, барьеры, спинлоки и условные переменные.
Мьютекс (англ. mutex, от mutual exclusion – взаимное исключе- ние) – объект синхронизации, обеспечивающий взаимно исключаю- щий доступ к ресурсу. Только поток, владеющий мьютексом, может работать с ресурсом.
В Pthreads мьютекс представляется специальным типом данных: pthread_mutex_t. Для использования мьютекса необходимо создать переменную типа pthread_mutex_t и инициализировать ее с помо- щью функции pthread_mutex_init(). Потоки, желающие получить доступ к ресурсу, защищенному мьютексом, должны его захватить.
В любой момент только один поток может владеть мьютексом. Если несколько потоков пытаются одновременно захватить один и тот же мьютекс, только один поток сможет это сделать. Захват мьютекса вы- полняется с помощью функции pthread_mutex_lock() либо функ- ции pthread_mutex_trylock(). Разница между этим функциями состоит в том, что вызов pthread_mutex_lock() приводит к блоки- рованию выполнения потока, если мьютекс уже захвачен другим пото-


23 ком, тогда как функция pthread_mutex_trylock() в этом случае вернет ошибку, и вызывающий поток продолжит свое исполнение.
Функция pthread_mutex_unlock() освобождает захваченный ранее мьютекс (освободить мьютекс может только тот поток, который его захватил), а функция pthread_mutex_destroy() освобождает ресур- сы, связанные с мьютексом, когда он больше не нужен.
Тип объекта синхронизации: pthread_mutex_t
Прототипы функций: int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); int pthread_mutex_destroy(pthread_mutex_t *mutex);
Параметры функций:
 mutex – указатель на переменную типа «мьютекс»;
 attr – указатель на атрибуты, которые получит мьютекс при ини- циализации (см. ниже); если указано NULL, то мьютекс получит ат- рибуты по умолчанию.
Сообщения об ошибках:
 ноль – ошибки отсутствуют; pthread_mutex_init():
 EAGAIN – недостаточно ресурсов (кроме памяти) для инициализа- ции мьютекса;
 ENOMEM – недостаточно памяти для инициализации мьютекса;
 EPERM – недостаточно прав для выполнения операции; pthread_mutex_lock() и pthread_mutex_trylock():
 EAGAIN – мьютекс не может быть захвачен из-за превышения мак- симального количества рекурсивных захватов;

24
 ENOTRECOVERABLE – невозможно восстановить целостное состоя- ние данных, защищенных мьютексом;
 EOWNERDEAD – поток, владевший мьютексом, завершился, не осво- бодив его; pthread_mutex_lock():
 EDEADLK – текущий поток уже захватил мьютекс; pthread_mutex_trylock():
 EBUSY – мьютекс не может быть захвачен, потому что его захватил другой поток; pthread_mutex_unlock():
 EPERM – поток не владеет мьютексом.
Важно отметить, что ответственность за корректное использование мьютексов (инициализация, захват, освобождение) полностью лежит на программисте.
Рассмотрим пример модельной задачи: имеется очередь заданий и два потока-исполнителя. Каждый поток забирает из очереди текущее задание, сдвигает указатель текущего задания на следующее задание, после чего выполняет взятое задание. Процесс повторяется до тех пор, пока все задания в очереди не будут выполнены. В примере 4 приво- дится программа, решающая эту задачу с использованием мьютекса.
Для обработки ошибок создан специальный макрос err_exit, ко- торый позволяет сократить объем кода, осуществляющего проверки на ошибки.
Пример 4. Использование мьютексов
#include
#include
#include
#include using namespace std;
#define err_exit(code, str) { cerr << str << ": " << strerror(code) \
<< endl; exit(EXIT_FAILURE); } const int TASKS_COUNT = 10; int task_list[TASKS_COUNT]; // Массив заданий