solaris % testcancel
thread1 got a read lock
thread2 trying to obtain a write lock
и мы никогда не вернемся к приглашению интерпретатора. Программа зависнет. Произошло вот что:
1. Второй поток вызвал pthread_rwlock_wrlock (листинг 8.6), которая была заблокирована в вызове pthread_cond_wait.
2. Первый поток вернулся из вызова slеер(3) и вызвал pthread_cancel.
3. Второй поток был отменен и завершил работу. При отмене потока, заблокированного в ожидании сигнала по условной переменной, взаимное исключение блокируется до вызова первого обработчика-очистителя. (Мы не устанавливали обработчик, но взаимное исключение все равно блокируется до завершения потока.) Следовательно, при отмене выполнения второго потока взаимное исключение осталось заблокированным и значение rw_nwaitwriters в листинге 8.6 было увеличено.
4. Первый поток вызывает pthread_rwlock_unlock и блокируется навсегда при вызове pthread_mutex_lock (листинг 8.8), потому что взаимное исключение все еще заблокировано отмененным потоком.
Если мы уберем вызов pthread_rwlock_unlock в функции thread1, функция main выведет вот что:
rw_refcount = 1, rw_nwaitreaders = 0, rw_nwaitwriters = 1
pthread_rwlock_destroy error: Device busy
Первый счетчик имеет значение 1, поскольку мы удалили вызов pthread_rwlock_ unlock, а последний счетчик имеет значение 1, поскольку он был увеличен вторым потоком до того, как тот был отменен.
Исправить эту проблему просто. Сначала добавим две строки к функции pthread_rwlock_rdlock в листинге 8.4. Строки отмечены знаком +:
rw-rw_nwaitreaders++;
+ pthread_cleanup_push(rwlock_cancelrdwait, (void *) rw);
result = pthread_cond_wait(rw-rw_condreaders, rw-rw_mutex);
+ pthread_cleanup_pop(0);
rw-rw_nwaitreaders++;
Первая новая строка устанавливает обработчик-очиститель (функцию rwlock_cancelrdwait), а его единственным аргументом является указатель rw. После возвращения из pthread_cond_wait вторая новая строка удаляет обработчик. Аргумент функции pthread_cleanup_pop означает, что функцию-обработчик при этом вызывать не следует. Если этот аргумент имеет ненулевое значение, обработчик будет сначала вызван, а затем удален.
Если поток будет отменен при вызове pthread_cond_wait, возврата из нее не произойдет. Вместо этого будут запущены обработчики (после блокирования соответствующего взаимного исключения, как мы отметили в пункте 3 чуть выше).
В листинге 8.10 приведен текст функции rwlock_cancelrdwait, являющейся обработчиком-очистителем для phtread_rwlock_rdlock.
//my_rwlock_cancel/pthread_rwlock_rdlock.с
3 static void
4 rwlock_cancelrdwait(void *arg)
5 {
6 pthread_rwlock_t *rw;
7 rw = arg;
8 rw-rw_nwaitreaders--;
9 pthread_mutex_unlock(rw-rw_mutex);
10 }
8-9 Счетчик rw_nwaitreaders уменьшается, а затем разблокируется взаимное исключение. Это состояние, которое должно быть восстановлено при отмене потока.
Аналогично мы исправим текст функции pthread_rwlock_wrlock из листинга 8.6. Сначала добавим две новые строки рядом с вызовом pthread_cond_wait:
rw-rw_nwaitreaders++;
+ pthread_cleanup_push(rwlock_cancelrwrwait, (void*) rw);
result = pthread_cond_wait(rw-rw_condwriters, rw-rw_mutex);
+ pthread_cleanup_pop(0);
rw-rw_nwaitreaders--;
В листинге 8.11 приведен текст функции rwlock_cancelwrwait, являющейся обработчиком-очистителем для запроса блокировки на запись.
//my_rwlock_cancel/pthread_rwlock_wrlock.с
3 static void
4 rwlock_cancelwrwait(void *arg)
5 {
6 pthread_rwlock_t *rw;
7 rw = arg;
8 rw-rw_nwaitwriters––;
9 pthread_mutex_unlock(rw-rw_mutex);
10 }
8-9 Счетчик rw_nwaitwriters уменьшается, и взаимное исключение разблокируется. При запуске нашей тестовой программы из листинга 8.9 с этими новыми функциями мы получим правильные результаты:
solaris %testcancel
thread1 got a read lock
thread2 trying to obtain a write lock