Шрифт:
// Листинг 5.3. Пример использования потоками блокировок
// чтения-записи
//...
pthread_t ThreadA, ThreadB, ThreadC, ThreadD ; pthread_rwlock_t RWLock;
void *producerl(void *X) {
pthread_rwlock_wrlock(&RWLock) ; // Критический раэдел.
pthread_rwlock_unlock(&RWLock) ; return(0);
}
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);