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

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

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

//Сдвигаем указатель текущего задания на следующее 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

31

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

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(). После ис-

пользования переменной, содержащей атрибуты барьера, связанные с

32

ней ресурсы необходимо освободить с помощью функции 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 – значение атрибута «видимость барьера» выходит за границы допустимых значений.

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

33

Пример 6. Использование барьера

#include <cstdlib> #include <cstring> #include <iostream> #include <pthread.h>

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<DATA_ITEMS/2; ++i)

data[i] = rand() % DATA_ITEMS;

//Ожидаем, когда другой поток инициализирует свою часть 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<DATA_ITEMS; ++i) *(int *)sum += data[i];

}

void *thread_func2(void *mult)

{

int err;

//Инициализируем часть массива случайными числами for(int i=DATA_ITEMS/2; i<DATA_ITEMS; ++i)

data[i] = rand() % DATA_ITEMS;

//Ожидаем, когда другой поток инициализирует свою часть err = pthread_barrier_wait(&barrier);

34

if((err != 0) && (err != PTHREAD_BARRIER_SERIAL_THREAD)) err_exit(err, "Cannot wait on barrier");

// Считаем разность элементов массива for(int i=0; i<DATA_ITEMS; ++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) – объект синхронизации, который позволяет приостановить выполнение потока до момента поступления сигнала от другого потока или до истечения заданного времени ожидания сигнала. В отличие от мьютексов, обеспечивающих синхронизацию потоков через управление доступом к ресурсам, условные переменные обеспечивают синхронизацию потоков на основе событий, происходящих с ресурсами.

35

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

Механизм условных переменных как раз и обеспечивает необходимую атомарность. Для его использования необходимо объявить и инициализировать с помощью функции pthread_cond_init() условную переменную, а также объявить и инициализировать мьютекс, который будет использоваться в паре с этой условной переменной. Поток, которому необходимо дождаться некоторого события, захватывает мьютекс. Затем он вызывает функцию pthread_cond_wait() для блокировки до момента наступления события, при этом мьютекс автоматически освобождается, а поток переводится в спящее состояние. Если необходимо ограничить время ожидания события, можно воспользоваться функцией pthread_cond_timedwait(), которая позволяет задать максимальный период ожидания. Когда другой поток подаст сигнал на условную переменную с помощью функции pthread_ cond_signal() или pthread_cond_broadcast(), произойдет вы-

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

36

destroy(). Следует подчеркнуть, что условные переменные не обеспечивают взаимного исключения.

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

Тип объекта синхронизации:

pthread_cond_t

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

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex,

const struct timespec *restrict abstime);

int pthread_cond_signal(pthread_cond_t *cond);

int pthread_cond_broadcast(pthread_cond_t *cond);

int pthread_cond_destroy(pthread_cond_t *cond);

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

cond – указатель на переменную типа «условная переменная»;

attr – указатель на атрибуты, которые получит условная переменная при инициализации; если указано NULL, то условная переменная получит атрибуты по умолчанию;

mutex – указатель на мьютекс, который будет использоваться в паре с условной переменной;

abstime – указатель на переменную, хранящую допустимое время ожидания сигнала.

37

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

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

EAGAIN – недостаточно ресурсов (кроме памяти) для инициализации условной переменной;

ENOMEM – недостаточно памяти для инициализации условной переменной;

pthread_cond_wait() и pthread_cond_timedwait():

ENOTRECOVERABLE – невозможно восстановить целостное состояние данных, защищенных мьютексом;

EOWNERDEAD – поток, владевший мьютексом, завершился, не освободив его;

EPERM – поток не владеет мьютексом;

pthread_cond_timedwait():

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

EINVAL – значение abstime выходит за границы допустимых значений.

Работа с атрибутами условной переменной похожа на работу с атрибутами других объектов. Необходимо объявить переменную типа pthread_condattr_t и инициализировать ее с помощью функции pthread_condattr_init(). Затем можно устанавливать значение атрибутов с помощью специализированных функций. Обязательным является атрибут clock_id, конкретная реализация Pthreads может определять дополнительные атрибуты. После использования переменной с атрибутами связанные с ней ресурсы необходимо освободить с помощью функции pthread_condattr_destroy().

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

int pthread_condattr_init(pthread_condattr_t *attr);

int pthread_condattr_destroy(pthread_condattr_t *attr);

int pthread_condattr_setclock(pthread_condattr_t *attr, clockid_t clock_id);

int pthread_condattr_getclock(const pthread_condattr_t *restrict attr, clockid_t *restrict clock_id);

38

int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);

int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr, int *restrict pshared);

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

attr – указатель на переменную, содержащую атрибуты условной переменной;

clock_id – тип таймера, который используется для условной переменной со временем ожидания; обязательно поддерживается только значение CLOCK_REALTIME, которое является значением по умолчанию (другие значения определяются реализацией);

pshared – видимость условной переменной; возможные значения приведены в табл. 4.

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

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

ENOMEM – недостаточно памяти для инициализации объекта с атрибутами условной переменной;

pthread_condattr_setclock():

EINVAL – значение атрибута «тип таймера» выходит за границы допустимых значений;

pthread_barrierattr_setpshared():

EINVAL – значение атрибута «видимость условной переменной» выходит за границы допустимых значений.

Вкачестве примера использования условных переменных приведем каркас решения задачи производитель–потребитель с одним пото- ком-производителем, одним потоком-потребителем и одним доступ-

ным местом на складе. Текст программы приведен в примере 7. Пример 7. Использование условных переменных

#include <cstdlib> #include <cstring> #include <iostream> #include <unistd.h> #include <pthread.h>

39

using namespace std;

#define err_exit(code, str) { cerr << str << ": " << strerror(code) \ << endl; exit(EXIT_FAILURE); }

enum store_state {EMPTY, FULL} state = EMPTY;

pthread_mutex_t mutex; pthread_cond_t cond;

int store;

void *producer(void *arg)

{

int err;

//

while(true) {

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

if(err != 0)

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

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

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

}

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

//Производим новый товар.

store = rand(); state = FULL;

// Посылаем сигнал, что на складе появился товар. 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");

}

40

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