Материал: Sb97573

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

pthread_mutex_lock(&mutex);

// 2

. . .

 

pthread_mutex_unlock(&mutex);

// 3

. . .

 

pthread_mutex_unlock(&mutex);

// 4

. . .

 

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

int pthread_mutexattr_setrecurcive(pthread_mutexattr_t* attr, int recursive); int pthread_mutexattr_getrecurcive(pthread_mutexattr_t* attr, int recursive);

где recursive:

PTHREAD_RECURSIVE_ENABLE – разрешить рекурсивный захват; PTHREAD_RECURSIVE_DISABLE – (по умолчанию) запретить.

И наконец, важная особенность реализации мьютекса связана с возможностью борьбы с проблемой «инверсии приоритетов». Однако эта ситуация относится к системам реального времени, рассмотрение которых выходит за рамки представленного материала.

Условная переменная

Следующая функция инициализирует условную переменную: int pthread_cond_init(pthread_cond_t* cond, pthread_condattr_t* attr),

где cond – указатель на описатель условной переменной; attr – указатель на атрибуты условной переменной.

Следующая функция приводит к ожиданию выполнения условия: int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex). Поток, в

котором вызвана эта функция, ставится в очередь, связанную с переменной cond (cond – указатель на описатель условной переменной; mutex – указатель на описатель мьютекса).

Следующая функция посылает сигнал о выполнении условия,

связанного с переменной cond: int pthread_cond_signal( pthread_cond_t* cond)

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

31

Следующая функция посылает сигнал о выполнении условия, связанного с переменной cond, всем потокам, заблокированным на этой переменной: int pthread_cond_broadcast( pthread_cond_t* cond ); все потоки,

заблокированные на переменной cond, переводятся в очередь потоков, готовых к выполнению.

В качестве примера приведем возможную реализацию функций записи и чтения в задаче «Поставщик – Потребитель»:

pthread_mutex_t mutexFull, mutexEmpty; pthread_cond_t condFull, condEmpty; int buffer_full = 0, buffer_empty = 1;

/* функция записи в буфер, вызываемая «Поставщиком» */ pthread_mutex_lock(&mutexFull);

while (buffer_full)

{

pthread_cond_wait(&condFull, &mutexFull);

}

/* Здесь подразумевается код, реализующий запись в буфер и установку значения переменной buffer_empty*/ pthread_cond_signal(&condEmpty); pthread_mutex_unlock(&mutexFull);

/* функция чтения из буфера, вызываемая «Потребителем»

*/ pthread_mutex_lock(&mutexEmpty); while (buffer_empty)

{

pthread_cond_wait(&condEmpty, &mutexEmpty);

}

/* Здесь подразумевается код, реализующий чтение из буфера и установку значения переменной buffer_full */ pthread_cond_signal(&condFull); pthread_mutex_unlock(&mutexEmpty);

32

7. ПРИМЕРЫ ЗАДАЧ СИНХРОНИЗАЦИИ И ИХ РЕАЛИЗАЦИЯ

Задача об обедающих философах

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

На столе, слева и справа от каждого места, лежат две вилки, и для того, чтобы есть спагетти, философу нужно взять обе вилки – левую и правую. Если допустить, что все 5 философов сели за стол, и каждый из них взял одну вилку (например, правую), то возникает тупиковая ситуация.

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

Задача состоит в том, чтобы предложить программную модель поведения философов, позволяющую избежать двух указанных проблем – тупиков и бесконечного зацикливания.

33

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

Модель поведения i-го философа можно представить следующим образом:

while (true) {

Думает(i); Захват_вилок(i); Обедает(i); Освобождение_вилок(i);

}

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

int const Nphil = 5.

При этом каждая вилка является общим ресурсом для двух соседних философов-потоков. В этих условиях задача сводится к реализации функции Захват_вилок(int philnum) и Освобождение_вилок(int philnum) (philnum –

номер философа), удовлетворяющей исходной постановке.

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

Функция Захват_вилок(int philnum) может быть реализована следующим образом. Когда философ с номером philnum решил пообедать, он проверяет состояния двух соседних вилок. Если хотя бы одна из них занята, философ переходит в состояние ожидания освобождения этих вилок. Если обе вилки свободны, философ захватывает их и начинает обедать.

Функция Освобождение_вилок(int philnum) может работать так: когда философ с номером philnum заканчивает обедать, он освобождает две соседние от него вилки. Это позволяет начать обедать двум соседним с ним философам, если вилки, которые находятся с другой стороны от этих философов, свободны, а сами философы ждут, когда смогут пообедать.

34

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

Прежде всего, для обеспечения режима взаимного исключения при работе с функциями Захват_вилок(int philnum) и Освобождение_вилок(int philnum) из разных потоков введем мьютекс: pthread_mutex_t mutex.

Для обеспечения ожидания потока-философа в случае занятости нужных вилок введем условные переменные: pthread_cond_t philCV[Nphil].

Далее объявим массив чисел, соответствующих состояниям вилок: int vil[Nphil]; //0 – вилка свободна; 1 – вилка занята

ивведем 4 массива, определяющих по номеру философа номера его левых и правых вилок, а также номера философов слева и справа от него:

int const vl[Nphil] = {0,1,2,3,4};//левая вилка; int const vp[Nphil] = {4,0,1,2,3};//правая вилка;

int const fl[Nphil] = {1,2,3,4,0};//левый философ; int const fp[Nphil] = {4,0,1,2,3};//правый философ.

Теперь текст функции Захват_вилок(int philnum) можно представить следующим образом:

void Захват_вилок(int philnum)

{

pthread_mutex_lock(&mutex); int Lv = vl[philnum];

//определение номера левой вилки по номеру философа int Rv = vp[philnum];

//определение номера правой вилки по номеру философа //если хотя бы одна из вилок занята, ожидание

if ((vil[Lv] == 1)||(vil[Rv] == 1)) { pthread_cond_wait(&philCV[philnum],&mutex);

}

vil[Lv] = 1;//захват вилок vil[Rv] = 1; pthread_mutex_unlock(&mutex);

}

Всоответствии с описанием работы функции Освобождение_вилок(int philnum) код функции будет выглядеть следующим образом:

void Освобождение_вилок(int philnum)

{

35

Источник: https://studfile.net/preview/16438914/