Новосибирский Государственный Университет
Опубликован: 28.04.2010 | Доступ: свободный | Студентов: 686 / 60 | Оценка: 4.62 / 4.24 | Длительность: 10:21:00
Лекция 7:

Блокировки чтения-записи, условные переменные, барьеры и семафоры-счетчики

< Лекция 6 || Лекция 7: 12 || Лекция 8 >

Барьеры

Барьер ( barrier ) - примитив синхронизации, применяемый главным образом в вычислительных задачах MPI/OpenMP. Существуют также примеры его применения в других задачах, но как правило это также задачи вычислительного характера. При создании барьера необходимо указать количество нитей N, необходимое для перехода через барьер. Нити, подходящие к барьеру, вызывают функцию pthread_barrier_wait(3C). Если количество нитей, ожидающих возле барьера, меньше N-1, нить блокируется. Когда набирается N нитей, все они разблокируются и продолжают исполнение.

При разблокировании в одной из нитей pthread_barrier_wait(3C) возвращает значение PTHREAD_BARRIER_SERIAL_THREAD, а в остальных нитях - 0. Рассмотрим расчет видеокадра многопоточной программой на многопроцессорной машине. Нити, завершившие расчет своей части изображения, блокируются на барьере. Когда расчет изображения завершен, одна из нитей (получившая PTHREAD_BARRIER_SERIAL_THREAD ) переключает видеоконтекст, то есть показывает изображение пользовтаелю, и нити продолжают расчеты.

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

Семафоры-счетчики

Исторически, семафоры-счетчики были одним из первых примитивов синхронизации. В старых учебниках они известны под названием "семафоры Дийкстры". Семафор представляет собой целочисленную переменную, над которой определены две операции, post и wait.

Операция wait пытается вычесть единицу из флаговой переменной семафора. Если значение флаговой переменной больше 0, то происходит обычное вычитание и нить, выполнившая операцию wait, продолжает исполнение. Если значение флаговой переменной было равно 0, то wait блокируется до тех пор, пока кто-то (скорее всего, другая нить) не увеличит значение этой переменной.

Операция post добавляет единицу к флаговой переменной семафора. Если при этом на семафоре ждала нить, операция post пробуждает эту нить.

Семафоры можно использовать в качестве примитивов взаимоисключения, при этом операция wait аналогична захвату мутекса, а операция post - операции его освобождения. Однако, в отличие от мутексов, операции post и wait не обязаны выполняться одной и той же нитью и даже не обязаны быть парными. Это позволяет использовать семафоры в различных ситуациях, которые сложно или невозможно разрешить при помощи мутексов. Иногда семафоры используют в качестве разделяемых целочисленных переменных, например в качестве счетчиков записей в очереди.

Широко известно решение задачи производитель-потребитель на двух семафорах (см. пример 7.3).

sem_t p, q; 
int data; 
void *producer(void *) { 
  while(1) { 
    int t=produce(); 
    sem_wait(&p); 
    data=t; 
    sem_post(&q); 
  } 
  return NULL; 
} 

void * consumer(void *) { 
  while (1) { 
    int t; 
    sem_wait(&q); 
    t=data; 
    sem_post(&p); 
    consume(1); 
  } 
  return NULL; 
}
7.3. Решение задачи производитель-потребитель на двух семафорах

Семафоры также можно использовать для обхода проблемы инверсии приоритета. Если высокоприоритетная и низкоприоритетная нить должны взаимодействовать, иногда удается реализовать соглашение, что только низкоприоритетная нить может выполнять над семафором операцию wait, а высокоприоритетная нить может делать только post. Такое взаимодействие обычно похоже на схему производитель-потребитель с тем отличием, что производитель может терять некоторые порции данных, если потребитель за ним не успевает. При этом условии в примере 7.3 можно избавиться от семафора p.

Такое решениедовольно часто используется в системах жесткого реального времени, поэтому семафоры-счетчики считаются частью стандарта POSIX Real Time Extension, а не основного подмножества стандарта POSIX.

В Solaris функции работы с семафорами-счетчиками включены в библиотеку librt.so. Их использование требует сборки программы с ключом -lrt. В отличие от остальных функций POSIX Thread API, функции работы с семафорами и сам тип семафора не имеют префикса pthread_.

Как и остальные примитивы взаимодействия, рассматривавшиеся ранее, семафор POSIX представляет собой непрозрачный тип данных, операции над которым должны осуществляться специальными функциями. Этот тип называется sem_t и определен в файле <semaphore.h>.

Семафоры бывают двух типов - именованные и неименованные. Те и другие семафоры хранятся в переменных типа sem_t, но процедура инициализации и уничтожения этих переменных отличается.

Неименованные семафоры инициализируются функцией sem_init(3RT). Эта функция имеет три параметра:

  • sem_t * sem - инициализируемый семафор
  • int pshared - 0 если семафор будет локальным в пределах процесса, ненулевое значение - если семафор будет разделяемым между процессами
  • unsigned intvalue - начальноезначение флаговой переменной семафора.

После работы семафор необходимо уничтожить функцией sem_destroy(3RT).

Над семафорами определены операции sem_post(3RT), sem_wait(3RT), sem_trywait(3RT), sem_timedwait(3RT) и sem_getvalue(3RT). Операция sem_getvalue(3RT), как сказано в системном руководстве, "получает значение семафора в некоторый неопределенный момент времени". В любом случае, очевидно, что в интервале между исполнением sem_getvalue(3RT) и проверкой значенияфлаговая переменная семафора может измениться. Поэтому тот факт, что sem_getvalue(3RT) вернул ненулевое значение, не означает, что вызов sem_wait(3RT) с этим семафором не будет заблокирован. sem_getvalue полезен главным образом в отладочных целях.

В соответствии со стандартом POSIX, если на семафоре ожидает одна или несколько нитей, sem_getvalue(3RT) вместо нуля может возвращать отрицательное значение, модуль которого равен количеству нитей. Это поведение не является обязательным и Solaris 10 этого не делает.

Именованные семафоры создаются функцией sem_open(3RT). При помощи этой же функции можно получить доступ к уже существующему именованному семафору. Эта функция имеет два обязательных параметра и два необязательных:

  • const char * name - имя семафора. Имя должно начинаться с символа ' / ' и не должно содержать других символов ' / '. Рекомендуется, чтобы имя не превышало 14 символов. В зависимости от реализации, объект с таким именем может либо появляться либо не появляться в корневом каталогекорневой файловой системы (в Solaris и Linux не появляется). В любом случае, для создания семафора не обязательно иметь право создания файлов в корневом каталоге.
  • int flags - флаги. Может принимать значения 0, O_CREAT и O_CREAT|O_EXCL, где O_CREAT и O_EXCL - константы, определенные в <sys/fcntl.h>. Смысл этих значений аналогичен соответствующим значениям флагов в параметрах open(2). 0 означает попытку доступа к уже существующему семафору, O_CREAT - доступ к существующему семафору или попытку создания, если такого семафора нет, O_EXCL - ошибку, если при попытке создания обнаруживается, что такой семафор уже существует.
  • mode_t mode - необязательный параметр, который используется, только если flags содержит бит O_CREAT. Обозначает права доступа к семафору, которые задаются девятибитовой маской доступа, похожей на маску доступа к файлам. Как и у файла, у семафора есть идентификаторы хозяина и группы. Идентификатор хозяина устанавливается равным эффективному идентификатору пользователя процесса, создавшего семафор, идентификатор группы - эффективному идентификатору группы процесса.
  • unsigned int value - необязательный параметр, который используется только если flags содержит бит O_CREAT. Содержит начальное значение флаговой переменной семафора при его создании.

Функция sem_open(3RT) возвращает указатель на семафор ( sem_t * ). При ошибке она возвращает нулевой указатель и устанавливает errno. Если процесс попытается несколько раз открыть один и тот же семафор, ему будут возвращать один и тот же указатель. Именованные семафоры всегда разделяемые между процессами. При доступе к существующему семафору проверяются права доступа по той же схеме, по которой в Unix системам проверяются права доступа к файлам. Для доступа к семафору процесс должен иметь права чтения и записи.

Для отсоединения от семафора и освобождения памяти из-под него необходимо использовать функцию sem_close(3RT). Эта функция неявно вызывается при exit(2) и exec(2). Однако закрытие именованного семафора процессом не прекращает существования семафора. Чтобы удалить семафор, необходимо вызвать функцию sem_unlink(3RT). Это лишит новые процессы возможность видеть семафор как существующий (попытка исполнить sem_open с именем этого семафора без флага O_CREAT приведет к ошибке) и позволит создать новый семафор с тем же именем. Однако если в момент sem_unlink(3RT) один или несколько процессов работали с семафором, семафор продолжит свое существование до момента, пока все эти процессы не выполнят sem_close(3RT). Исполнять sem_unlink(3RT) могут только владелец семафора и суперпользователь.

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

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

< Лекция 6 || Лекция 7: 12 || Лекция 8 >