Опубликован: 06.12.2004 | Доступ: свободный | Студентов: 1179 / 142 | Оценка: 4.76 / 4.29 | Длительность: 20:58:00
ISBN: 978-5-9556-0021-5
Лекция 2:

Средства синхронизации потоков управления

Переменные условия

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

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

while (! предикат) {
    Ожидание на переменной условия
    с освобождением мьютекса.
    После успешного завершения
    ожидания поток вновь 
    оказывается владельцем мьютекса.
}

После нормального выхода из цикла проверяемое условие истинно; можно выполнить требуемые действия и освободить мьютекс.

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

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

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

Переходя к описанию функций, предлагаемых стандартом POSIX-2001 для обслуживания переменных условия, укажем, что их можно разделить на следующие группы:

  • инициализация и разрушение переменных условия: pthread_cond_init(), pthread_cond_destroy() (см. листинг 2.12);
    #include <pthread.h>
    
    int pthread_cond_init (
      pthread_cond_t *restrict cond,
      const pthread_condattr_t
        *restrict attr);
    
    int pthread_cond_destroy (
       pthread_cond_t *cond);
    
    pthread_cond_t cond =
      PTHREAD_COND_INITIALIZER;
    Листинг 2.12. Описание функций инициализации и разрушения переменных условия.
  • блокирование (ожидание) на переменной условия: pthread_cond_wait(), pthread_cond_timedwait() (см. листинг 2.13);
    #include <pthread.h>
    
    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, 
      struct timespec *restrict abstime);
    Листинг 2.13. Описание функций блокирования на переменной условия.
  • разблокирование (прекращение ожидания) потоков управления, блокированных на переменной условия: pthread_cond_broadcast(), pthread_cond_signal() (см. листинг 2.14);
    #include <pthread.h>
    
    int pthread_cond_broadcast (
      pthread_cond_t *cond);
    
    int pthread_cond_signal (
      pthread_cond_t *cond);
    Листинг 2.14. Описание функций разблокирования потоков управления, блокированных на переменной условия.
  • инициализация и разрушение атрибутных объектов переменных условия: pthread_condattr_init(), pthread_condattr_destroy() (см. листинг 2.15);
    #include <pthread.h>
    
    int pthread_condattr_init (
      pthread_condattr_t *attr);
    
    int pthread_condattr_destroy (
      pthread_condattr_t *attr);
    Листинг 2.15. Описание функций инициализации и разрушения атрибутных объектов переменных условия.
  • опрос и установка атрибутов переменных условия в атрибутных объектах: признака использования несколькими процессами (обслуживается функциями pthread_condattr_getpshared(), pthread_condattr_setpshared() ) и идентификатора часов реального времени, используемых для ограничения ожидания на переменной условия (функции pthread_condattr_getclock(), pthread_condattr_setclock() ) (см. листинг 2.16).
    #include <pthread.h>
    
    int pthread_condattr_getpshared (
      const pthread_condattr_t 
      *restrict attr, int *restrict pshared);
    
    int pthread_condattr_setpshared (
      pthread_condattr_t *attr, 
      int pshared);
    
    int pthread_condattr_getclock (
      const pthread_condattr_t 
      *restrict attr, clockid_t
        *restrict clock_id);
    
    int pthread_condattr_setclock (
      pthread_condattr_t *attr, 
      clockid_t clock_id);
    Листинг 2.16. Описание функций опроса и установки атрибутов переменных условия в атрибутных объектах.