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

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

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

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

Добавлен: 23.11.2023

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

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

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

25 int current_task = 0; //
Указатель на текущее задание pthread_mutex_t mutex;
// Мьютекс void do_task(int task_no)
{
/* Сюда необходимо поместить код, выполняющий задание */
} void *thread_job(void *arg)
{ int task_no; int err;
// Перебираем в цикле доступные задания while(true) {
// Захватываем мьютекс для исключительного доступа
// к указателю текущего задания (переменная
// current_task) err = pthread_mutex_lock(&mutex); if(err != 0) err_exit(err, "Cannot lock mutex");
// Запоминаем номер текущего задания, которое будем исполнять task_no = current_task;
// Сдвигаем указатель текущего задания на следующее current_task++;
// Освобождаем мьютекс err = pthread_mutex_unlock(&mutex); if(err != 0) err_exit(err, "Cannot unlock mutex");
// Если запомненный номер задания не превышает
// количества заданий, вызываем функцию, которая
// выполнит задание.
// В противном случае завершаем работу потока if(task_no < TASKS_COUNT) do_task(task_no); else return NULL;
}
}

26 int main()
{ pthread_t thread1, thread2;
// Идентификаторы потоков int err;
// Код ошибки
// Инициализируем массив заданий случайными числами for(int i=0; i // Инициализируем мьютекс err = pthread_mutex_init(&mutex, NULL); if(err != 0) err_exit(err, "Cannot initialize mutex");
// Создаём потоки err = pthread_create(&thread1, NULL, thread_job, NULL); if(err != 0) err_exit(err, "Cannot create thread 1"); err = pthread_create(&thread2, NULL, thread_job, NULL); if(err != 0) err_exit(err, "Cannot create thread 2"); pthread_join(thread1, NULL); pthread_join(thread2, NULL);
// Освобождаем ресурсы, связанные с мьютексом pthread_mutex_destroy(&mutex);
}
Работа с атрибутами мьютекса производится по аналогии с работой с атрибутами потока. Атрибуты мьютекса представляются типом pthread_mutexattr_t. Переменную этого типа перед использовани- ем необходимо инициализировать с помощью функции pthread_ mutexattr_init(), а после использования освободить связанные с ней ресурсы с помощью функции pthread_mutexattr_destroy().
Конкретный набор атрибутов мьютекса зависит от реализации.
Чаще всего используются атрибут type (тип мьютекса), который мож- но установить функцией pthread_mutexattr_settype(), а прочи- тать функцией pthread_mutexattr_gettype(), и атрибут pshared – для работы с ним применяются функции pthread_mutexattr_ setpshared() и pthread_mutexattr_getpshared().

27
Прототипы функций: int pthread_mutexattr_init(pthread_mutexattr_t *attr); int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type); int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr, int *restrict type); int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared); int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);
Параметры функций:
 attr – атрибуты мьютекса;
 type – тип мьютекса (табл. 3);
 pshared – видимость мьютекса (табл. 4);
Сообщения об ошибках:
 ноль – ошибки отсутствуют; pthread_mutexattr_init():
 ENOMEM – недостаточно памяти для инициализации объекта с атри- бутами мьютекса; pthread_mutexattr_settype():
 EINVAL – значение атрибута «тип мьютекса» некорректно; pthread_mutexattr_setpshared():
 EINVAL – значение атрибута «видимость мьютекса» выходит за границы допустимых значений.
Атрибут мьютекса type влияет на поведение функций, захватыва- ющих и освобождающих мьютекс. Всего стандарт определяет четыре типа мьютекса:
 PTHREAD_MUTEX_NORMAL не допускает повторного захвата мью- текса; при попытке повторного захвата мьютекса потоком, который является его владельцем, происходит дедлок;


28
 PTHREAD_MUTEX_ERRORCHECK не допускает повторного захвата мьютекса; предотвращает возникновение дедлоков, возвращая ошибку при попытке повторного захвата неосвобожденного мьютекса его владельцем;
 PTHREAD_MUTEX_RECURSIVE допускает рекурсивный захват мьютекса одним и тем же потоком, однако затем поток должен столько же раз освободить мьютекс;
 PTHREAD_MUTEX_DEFAULT является значением по умолчанию. Реа- лизация Pthreads может отображать это значение на один из других трех типов.
Сводные характеристики различных типов мьютексов приведены в табл. 3.
Т а б л и ц а 3
Возможные значения атрибута type
Значение
Повторная блокировка
Освобождение чужого или свободного
PTHREAD_MUTEX_NORMAL
Дедлок
Не определено
PTHREAD_MUTEX_ERRORCHECK
Возвращает ошибку
Возвращает ошибку
PTHREAD_MUTEX_RECURSIVE
Допускается
Возвращает ошибку
PTHREAD_MUTEX_DEFAULT
Не определено
Не определено
Атрибут мьютекса pshared определяет возможность использова- ния мьютекса потоками, принадлежащими разным процессам. Воз- можные значения атрибута приведены в табл. 4.
Т а б л и ц а 4
1   2   3   4   5   6   7

Возможные значения атрибута pshared
Значение
Описание
PTHREAD_PROCESS_PRIVATE
Позволяет использовать мьютекс в рамках одного процесса (синхронизироваться мо- гут только потоки, принадлежащие одному процессу). Является значением по умолча- нию
PTHREAD_PROCESS_SHARED
Позволяет использовать мьютекс для син- хронизации потоков, принадлежащих раз- ным процессам (при этом процессы долж- ны иметь доступ к памяти, где инициали- зирован мьютекс)

29
С
ПИНЛОКИ
Спинлок (англ. spinlock – циклическая блокировка) – объект син- хронизации, обеспечивающий взаимно исключающий доступ к ресур- су. Внешне спинлок очень похож на мьютекс, имеет аналогичный набор функций и принципы работы с ним. Разница заключается в тех- нической реализации.
В случае мьютекса, если потоку не удается захватить блокировку, происходит переключение в режим ядра ОС
3
, где поток переводится в состояние «спящий» (см. раздел «Планирование потоков») и перестает расходовать ресурсы системы до момента освобождения мьютекса.
В случае спинлока блокировка осуществляется в пользовательском режиме, причем поток не засыпает, а постоянно проверяет в цикле возможность захвата мьютекса, расходуя, таким образом, системные ресурсы.
В отличие от мьютекса, при захвате которого поток может произ- водить достаточно длительные операции, спинлок в силу реализации рассчитан на кратковременные вычисления, при этом повышается производительность за счет отсутствия переключения в режим ядра.
Внутри области, защищаемой спинлоком, нельзя переключать кон- текст (отдавать процессор другому потоку), а также вызывать функ- ции, которые могут выполнить это действие, так как это может приве- сти к дедлоку.
Тип объекта синхронизации: pthread_spinlock_t
Прототипы функций: int pthread_spin_init(pthread_spinlock_t *lock, int pshared); int pthread_spin_destroy(pthread_spinlock_t *lock); int pthread_spin_lock(pthread_spinlock_t *lock); int pthread_spin_trylock(pthread_spinlock_t *lock); int pthread_spin_unlock(pthread_spinlock_t *lock);
3
В зависимости от операционной системы и конкретной реализации стан- дарта POSIX Threads технические детали могут существенно различаться.

30
Параметры функций:
 lock – указатель на переменную типа «спинлок»;
 pshared – видимость спинлока; возможные значения приведены в табл. 3.
Сообщения об ошибках:
 ноль – ошибки отсутствуют; pthread_spin_init():
 EAGAIN – недостаточно ресурсов (кроме памяти) для инициализа- ции спинлока;
 ENOMEM – недостаточно памяти для инициализации спинлока; pthread_spin_trylock():
 EBUSY – спинлок не может быть захвачен, потому что его захватил другой поток.
В примере 5 приведены фрагменты кода решения задачи из приме- ра 4 с тем изменением, что функции для работы с мьютексом замене- ны на функции для работы со спинлоком (показан только изменив- шийся код). Так как внутри области, защищаемой спинлоком, выпол- няется всего несколько операций, его применение вместо мьютекса оправдано.
Пример 5. Использование спинлоков pthread_spinlock_t lock;
// Спинлок void *thread_job(void *arg)
{
// Захватываем спинлок err = pthread_spin_lock(&lock); if(err != 0) err_exit(err, "Cannot lock spinlock");
// Запоминаем номер текущего задания, которое будем исполнять task_no = current_task;


31
// Сдвигаем указатель текущего задания на следующее current_task++;
// Освобождаем спинлок pthread_spin_unlock(&lock);
} int main()
{
// Инициализируем спинлок err = pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE); if(err != 0) err_exit(err, "Cannot initialize spinlock");
// Освобождаем ресурсы, связанные со спинлоком pthread_spin_destroy(&lock);
}
Б
АРЬЕРЫ
Барьер (англ. barrier) – объект синхронизации, который позволяет приостановить выполнение потоков до того момента, пока все потоки не дойдут до некоторой точки в программе.
Прежде чем работать с барьером, его необходимо инициализиро- вать с помощью функции pthread_barrier_init(). Далее потоки могут вызывать функцию pthread_barrier_wait(), которая забло- кирует их в месте вызова до тех пор, пока количество потоков, вы- звавших pthread_barrier_wait(), не достигнет значения, указан- ного при инициализации барьера, после чего все потоки продолжат исполнение. Когда барьер станет ненужным (в том числе ни один по- ток не ожидает на барьере), необходимо освободить связанные с ним ресурсы с помощью функции pthread_barrier_destroy().
Тип объекта синхронизации: pthread_barrier_t

32
Прототипы функций:
int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned count); int pthread_barrier_destroy(pthread_barrier_t *barrier); int pthread_barrier_wait(pthread_barrier_t *barrier);
Параметры функций:
 barrier – указатель на переменную типа «барьер»;
 attr – указатель на атрибуты, которые получит барьер при иници- ализации; если указано NULL, то барьер получит атрибуты по умолчанию;
 count – количество потоков, которые должны вызвать функцию pthread_barrier_wait().
Сообщения об ошибках:
 ноль – ошибки отсутствуют; pthread_barrier_init():
 EAGAIN – недостаточно ресурсов (кроме памяти) для инициализа- ции барьера;
 EINVAL – указано нулевое количество потоков, которые должны вызвать функцию pthread_barrier_wait();
 ENOMEM – недостаточно памяти для инициализации барьера.
Функция pthread_barrier_wait() возвращает константу
PTHREAD_BARRIER_SERIAL_THREAD одному (любому) потоку и нуле- вое значение всем остальным потокам.
Работа с атрибутами барьера похожа на работу с атрибутами дру- гих объектов. Необходимо объявить переменную типа pthread_ barrierattr_t и инициализировать ее с помощью функции pthread_barrierattr_init(). Затем можно устанавливать значе- ние атрибутов с помощью специализированных функций. Конкретный перечень функций зависит от реализации Pthreads. Например, реализа- ции, поддерживающие опцию «Thread Process-Shared Synchronization», должны предоставлять функции pthread_barrierattr_ setpshared() и pthread_barrierattr_getpshared(). После ис- пользования переменной, содержащей атрибуты барьера, связанные с


33 ней ресурсы необходимо освободить с помощью функции pthread_barrierattr_destroy().
Прототипы функций: int pthread_barrierattr_init(pthread_barrierattr_t *attr); int pthread_barrierattr_destroy(pthread_barrierattr_t *attr); int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr, int pshared); int pthread_barrierattr_getpshared(const pthread_barrierattr_t
*restrict attr, int *restrict pshared);
Параметры функций:
 attr – указатель на переменную, содержащую атрибуты барьера;
 pshared – видимость барьера; возможные значения приведены в табл. 4.
Сообщения об ошибках:
 ноль – ошибки отсутствуют; pthread_barrierattr_init():
 ENOMEM – недостаточно памяти для инициализации объекта с атри- бутами барьера; pthread_barrierattr_setpshared():
 EINVAL – значение атрибута «видимость барьера» выходит за гра- ницы допустимых значений.
Рассмотрим пример использования барьеров. Пусть имеется массив данных и необходимо посчитать сумму и разность всех его элементов.
Прежде всего необходимо заполнить массив данными. Для простоты заполним его случайными значениями, причем для ускорения вычис- лений разделим эту работу между двумя потоками, так что каждый поток инициализирует лишь половину массива. Задачу по расчету суммы и разности всех элементов массива также разделим между по- токами: один поток будет считать сумму всех элементов массива, а другой – разность всех элементов. Ни один из потоков не может начать подсчет суммы или разности, пока массив не будет полностью иници- ализирован значениями.

34
Пример 6. Использование барьера
#include
#include
#include
#include using namespace std;
#define err_exit(code, str) { cerr << str << ": " << strerror(code) \
<< endl; exit(EXIT_FAILURE); } const int DATA_ITEMS = 1000; int data[DATA_ITEMS];
// Массив данных pthread_barrier_t barrier;
// Объект синхронизации «барьер» void *thread_func1(void *sum)
{ int err;
// Инициализируем часть массива случайными числами for(int i=0; i // Ожидаем, когда другой поток инициализирует свою часть err = pthread_barrier_wait(&barrier); if((err != 0) && (err != PTHREAD_BARRIER_SERIAL_THREAD)) err_exit(err, "Cannot wait on barrier");
// Считаем сумму элементов массива for(int i=0; i *(int *)sum += data[i];
} void *thread_func2(void *mult)
{ int err;
// Инициализируем часть массива случайными числами for(int i=DATA_ITEMS/2; i // Ожидаем, когда другой поток инициализирует свою часть err = pthread_barrier_wait(&barrier);

35 if((err != 0) && (err != PTHREAD_BARRIER_SERIAL_THREAD)) err_exit(err, "Cannot wait on barrier");
// Считаем разность элементов массива for(int i=0; i *(int *)mult -= data[i];
} int main()
{ int sum = 0, diff = 0;
// Сумма и разность элементов массива pthread_t thread1, thread2; // Идентификаторы потоков int err;
// Код ошибки
// Инициализируем барьер err = pthread_barrier_init(&barrier, NULL, 2); if(err != 0) err_exit(err, "Cannot initialize barrier");
// Создаём потоки err = pthread_create(&thread1, NULL, thread_func1, &sum); if(err != 0) err_exit(err, "Cannot create thread 1"); err = pthread_create(&thread2, NULL, thread_func2, &diff); if(err != 0) err_exit(err, "Cannot create thread 2"); pthread_join(thread1, NULL); pthread_join(thread2, NULL); cout << "Sum = " << sum << "; Diff = " << diff << endl;
// Освобождаем ресурсы, связанные с барьером pthread_barrier_destroy(&barrier);
}
У
СЛОВНЫЕ ПЕРЕМЕННЫЕ
Условная переменная (англ. condition variable) – объект синхрони- зации, который позволяет приостановить выполнение потока до мо- мента поступления сигнала от другого потока или до истечения задан- ного времени ожидания сигнала. В отличие от мьютексов, обеспечи- вающих синхронизацию потоков через управление доступом к ресур- сам, условные переменные обеспечивают синхронизацию потоков на основе событий, происходящих с ресурсами.