Средства синхронизации потоков управления
Переменные условия
Пусть имеется некоторый предикат (условие), зависящий от значений переменных, разделяемых несколькими потоками управления. Совместное использование мьютексов, переменных условия и обслуживающих их функций позволяет организовать экономное ожидание состояния истинности этого предиката.
Общая схема применения переменных условия выглядит следующим образом. С разделяемыми переменными, фигурирующими в предикате, ассоциируется мьютекс, который необходимо захватить перед началом проверок. Затем поток управления входит в цикл вида
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. Описание функций опроса и установки атрибутов переменных условия в атрибутных объектах.