}
void *producer2 (void *X) {
pthread_rwlock_wrlock(&RWLock) ; // Критический раздел.
pthread_rwlock_unlock(&RWLock) ;
}
void *consumerl(void *X) {
pthread_rwlock_rdlock(&RWLock); // Критический раздел.
pthread_rwlock_unlock(&RWLock); return(0);
}
void *consumer2(void *X) {
pthread_rwlock_rdlock(&RWLock); // Критический раздел.
pthread_rwlock__unlock(&RWLock); return(0);
}
int main(void) {
pthread_rwlock_init(&RWLock,NULL); // Устанавливаем атрибуты мьютекса. pthread_create(&ThreadA, NULL, producerl, NULL) pthread_create(&ThreadB, NULL, consumerl, NULL) pthread_create(&ThreadC,NULL,producer2,NULL) pthread_create(&ThreadD,NULL, consumer2,NULL) //.. .
return(0);
}
В листинге 5.3 создаются четыре потока. Два потока, ThreadA и ThreadC, выполняют роль изготовителей, а остальные два (ThreadB и ThreadD) — потребителей. Все потоки имеют критический раздел, который защищается объектом блокировки чтения-записи RWLock. Потоки ThreadB и ThreadD могут входить в свои критические разделы параллельно или последовательно, но это исключено, если поток ThreadA или ThreadC пребывает в своем критическом разделе. Потоки ThreadA и ThreadC не могут входить в свои критические разделы параллельно. Частичная таблица решении для листинга 5.3 показана в табл. 5.6.
Таблица 5.6. Час т ич н ая таблица решений для листинга 5.3
Поток А
Поток В
Поток С
Поток D
(выполняет запись)
(выполняет чтение)
(выполняет запись)
(выполняет чтение)
Нет
Нет
Нет
Да
Нет
Нет
Да
Нет
Нет
Да
Нет
Нет
Нет
Да
Нет
Да
Да
Нет
Нет
Нет
Условные переменные
Условная переменная представляет собой семафор, используемый для сигнализации о событии, которое произошло. Сигнала о том, что произошло некоторое событие, может ожидать один или несколько процессов (или потоков) от других процессов или потоков. Следует понимать различие между условными переменными и рассмотренными выше мьютексными семафорами. Назначение мьютексного семафора и блокировок чтения-записи — синхронизировать доступ к данным, в то время как условные переменные обычно используются для синхронизации последовательности операций. По этому поводу в своей книге UNIX Network Programming прекрасно высказался Ричард Стивенс (W. Richard Stevens): « Мьютексы нужно использовать для блокирования, а не для ожидания ».
В листинге 4.6 поток-«потребитель» содержал цикл:
15 while(TextFiles.empty())
16 {}
Поток-«потребитель» выполнял итерации цикла до тех пор, пока в очереди TextFiles были элементы. Этот цикл можно заменить условной пере м енной. Поток-«изготовитель» сигналом уведомляет потребителя о том, что в очередь помещены элементы. Поток-«потребитель» может ожидать до тех пор, пока не получит сигнал, а затем перейдет к обработке очереди.
Условная переменная имеет тип pthread_cond_t. Ниже перечислены типы операций, которые может она выполнять:
• инициализация;
• разрушение;
• ожидание;
• ожидание с ограничением по времени;
• адресная сигнализация;
• всеобщая сигнализация;
Операции инициализации и разрушения выполняются условными переменными подобно аналогичным операциям других мьютексов. Функции класса pthread_cond_t, которые реализуют эти операции, перечислены в табл. 5.7.
Ожидание
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);
Инициализация
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
pthread_cond_t cond =PTHREAD_C OND_INITIALIZER;
Таблица 5.7. Функции класса pthread_cond_t, которые реализуют операции условных переменных
Условные переменные используются совместно с мьютексами. При попытке заблокировать мьютекс поток или процесс будет заблокирован до тех пор, пока мьютекс не освободится. После разблокирования поток или процесс получит мьютекс и продолжит свою работу. При использовании условной переменной ее необходимо связать с мьютексом.
//. . .
pthread_mutex_lock(&Mutex) ;
pthread_cond_wait(&EventMutex, &Mutex);
//. . .
pthread_mutex_unlock(&Mutex) ;
Итак, некоторая задача делает попытку заблокировать мьютекс. Если мьютекс уже заблокирован, то эта задача блокируется. После разблокирования задача освободит мьютекс Mutex и при этом будет ожидать сигнала для условной переменной EventMutex . Если мьютекс не заблокирован, задача будет ожидать сигнала неограниченно долго. При ожидании с ограничением по времени задача будет ожидать сигнала в течение заданного интервала времени. Если это время истечет до получения задачей сигнала, функция возвратит код ошибки. Затем задача вновь затребует мьютекс.
Выполняя адресную сигнализацию, задача уведомляет другой поток или процесс о том, что произошло некоторое событие. Если задача ожидает сигнала для заданной условной переменной, эта задача будет разблокирована и получит мьютекс. Если сразу несколько задач ожидают сигнала для заданной условной переменной, то разблокирована будет только одна из них. Остальные задачи будут ожидать в очереди, и их разблокирование будет происходить в соответствии с используемой стратегией планирования. При выполнении операции всеобщей сигнализации уведомление получат все задачи, ожидающие сигнала для заданной условной переменной. При разблокировании нескольких задач они будут состязаться за право владения мьютексом в соответствии с используемой стратегией планирования. В отличие от операции ожидания, задача, выполняющая операцию сигнализации, не предъявляет прав на владение мьютексом, хотя это и следовало бы сделать.
Условная переменная также имеет атрибутный объект, функции которого перечислены в табл. 5.8.
Таблица 5.8. Функции доступа к атрибутному объекту для условной переменной типа pthread_cond_t
• int pthread_condattr_init ( pthread_condattr_t * attr) Инициализирует атрибутный объект условной переменной, заданный параметром attr, значениями, действующими по умолчанию для всех атрибутов, определенных реализацией;
• int pthread_condattr_destroy ( pthread_condattr_t * attr) ; Разрушает атрибутный объект условной переменной, заданный параметром attr. Этот объект можно инициализировать повторно, вы-звав функцию pthread_condattr_init ()
• int pthread_condattr_setpshared ( pthread_condattr_t * attr,int pshared);
• int pthread_condattr_getpshared ( const pthread_condattr_t * restrict attr, int *restrict pshared); Устанавливает или возвращает атрибут process-shared атрибутного объекта условной переменной, заданного параметром attr. Параметр pshared может содержать следующие значения: