Уильям Стивенс - UNIX: взаимодействие процессов
//my_rwlock/pthread_rwlock_tryrdlock.с
1 #include "unpipc.h"
2 #include "pthread_rwlock.h"
3 int
4 pthread_rwlock_tryrdlock(pthread_rwlock_t *rw)
5 {
6 int result;
7 if (rw->rwjnagic != RW_MAGIC)
8 return(EINVAL);
9 if ((result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
10 return(result);
11 if (rw->rw_refcount < 0 || rw->rw_nwaitwriters > 0)
12 result = EBUSY; /* блокировка установлена пишущим потоком или есть пишущие потоки, ожидающие освобождения ресурса */
13 else
14 rw->rw_refcount++; /* увеличение количества блокировок на чтение */
15 pthread_mutex_unlock(&rw->rw_mutex);
16 return(result);
17 }
11-14 Если блокировка в данный момент установлена на запись или есть процессы, ожидающие возможности установить ее на запись, возвращается ошибка с кодом EBUSY. В противном случае мы устанавливаем блокировку, увеличивая значение счетчика rw_refcount.
Функция pthread_rwlock_wrlock
Текст функции pthread_rwlock_wrlock приведен в листинге 8.6.
11-17 Если ресурс заблокирован на считывание или запись (значение rw_refcount отлично от 0), мы приостанавливаем выполнение потока. Для этого мы увеличиваем rw_nwaitwriters и вызываем pthread_cond_wait с условной переменной rw_condwriters. Для этой переменной посылается сигнал при снятии блокировки чтения-записи, если имеются ожидающие разрешения на запись процессы.
18-19 После получения блокировки на запись мы устанавливаем значение rw_refcount в –1.
Листинг 8.6. Функция pthread_rwlock_wrlock: получение блокировки на запись//my_rwlock/pthread_rwlock_wrlock.c
1 #include "unpipc.h"
2 #include "pthread_rwlock.h"
3 int
4 pthread_rwlock_wrlock(pthread_rwlock_t *rw)
5 {
6 int result;
7 if (rw->rw_magic != RW_MAGIC)
8 return(EINVAL);
9 if ((result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
10 return(result);
11 while (rw->rw_refcount != 0) {
12 rw->rw_nwaitwriters++;
13 result = pthread_cond_wait(&rw->rw_condwriters, &rw->rw_mutex);
14 rw->rw_nwaitwriters--;
15 if (result != 0)
16 break;
17 }
18 if (result == 0)
19 rw->rw_refcount = –1;
20 pthread_mutex_unlock(&rw->rw_mutex);
21 return(result);
22 }
Функция pthread_rwlock_trywrlock
Неблокируемая функция pthread_rwlock_trywrlock показана в листинге 8.7.
11-14 Если значение счетчика rw_refcount отлично от нуля, блокировка в данный момент уже установлена считывающим или записывающим процессом (это безразлично) и мы возвращаем ошибку с кодом EBUSY. В противном случае мы устанавливаем блокировку на запись, присвоив переменной rw_refcount значение –1.
Листинг 8.7. Функция pthread_rwlock_trywrlock: попытка получения блокировки на запись//my_rwlock/pthread_rwlock_trywrlock.c
1 #include "unpipc.h"
2 #include "pthread_rwlock.h"
3 int
4 pthread_rwlock_trywrlock(pthread_rwlock_t *rw)
5 {
6 int result;
7 if (rw->rw_magic != RW_MAGIC)
8 return(EINVAL);
9 if ((result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
10 return(result);
11 if (rw->rw_refcount != 0)
12 result = EBUSY; /* заблокирован пишущим потоком или ожидающим возможности записи */
13 else
14 rw->rw_refcount = –1; /* доступна */
15 pthread_mutex_unlock(&rw->rw_mutex);
16 return(result);
17 }
Функция pthread_rwlock_unlock
Последняя функция, pthread_rwlock_unlock, приведена в листинге 8.8.
Листинг 8.8. Функция pthread_rwlock_unlock: разблокирование ресурса//my_rwlock/pthread_rwlock_unlock.c
1 #include "unpipc.h"
2 #include "pthread_rwlock.h"
3 int
4 pthread_rwlock_unlock(pthread_rwlock_t *rw)
5 {
6 int result;
7 if (rw->rw_magic != RW_MAGIC)
8 return(EINVAL);
9 if ((result = pthread_mutex_lock(&rw->rw_mutex)) != 0)
10 return(result);
11 if (rw->rw_refcount > 0)
12 rw->rw_refcount--; /* снятие блокировки на чтение */
13 else if (rw->rw_refcount == –1)
14 rw->rw_refcount = 0; /* снятие блокировки на запись */
15 else
16 err_dump("rw_refcount = %d", rw->rw_refcount);
17 /* преимущество отдается ожидающим возможности записи потокам */
18 if (rw->rw_nwaitwriters > 0) {
19 if (rw->rw_refcount == 0)
20 result = pthread_cond_signal(&rw->rw_condwriters);
21 } else if (rw->rw_nwaitreaders > 0)
22 result = pthread_cond_broadcast(&rw->rw_condreaders);
23 pthread_mutex_unlock(&rw->rw_mutex);
24 return(result);
25 }
11-16 Если rw_refcount больше 0, считывающий поток снимает блокировку на чтение. Если rw_refcount равно –1, записывающий поток снимает блокировку на запись.
17-22 Если имеются ожидающие разрешения на запись потоки, по условной переменной rw_condwriters передается сигнал (если блокировка свободна, то есть значение счетчика rw_refcount равно 0). Мы знаем, что только один поток может осуществлять запись, поэтому используем функцию pthread_cond_signal. Если нет потоков, ожидающих возможности записи, но есть потоки, ожидающие возможности чтения, мы вызываем pthread_cond_broadcast для переменной rw_condreaders, поскольку возможно одновременное считывание несколькими потоками. Обратите внимание, что мы перестаем устанавливать блокировку для считывающих потоков, если появляются потоки, ожидающие возможности записи. В противном случае постоянно появляющиеся потоки с запросами на чтение могли бы заставить поток, ожидающий возможности записи, ждать целую вечность. По этой причине мы используем два отдельных оператора if и не можем написать просто:
/* предпочтение отдается записывающим процессам */
if (rw->rw_nwaitreaders > 0 && rw->rw_refcount == 0)
result = pthread_cond_signal(&rw->rw_condwriters);
else if (rw->rw_nwaitreaders > 0)
result = pthread_cond_broadcast(&rw->rw_condreaders);
Мы могли бы исключить и проверку rw->rw_refcount, но это может привести к вызовам pthread_cond_signal даже при наличии блокировок на чтение, что приведет к потере эффективности.
8.5. Отмена выполнения потоков
Обсуждая листинг 8.4, мы обратили внимание на наличие проблемы, возникающей при отмене выполнения потока, заблокированного вызовом pthread_cond_wait. Выполнение потока может быть отменено в том случае, если какой-нибудь другой поток вызовет функцию pthread_cancel, единственным аргументом которой является идентификатор потока, выполнение которого должно быть отменено:
#include <pthread.h>
int pthread_cancel(pthread_t tid);
/* Возвращает 0 в случае успешного завершения, положительное значение Еххх –в случае ошибки */
Отмена выполнения может быть использована в том случае, если несколько потоков начинают работу над какой-то задачей (например, поиск записи в базе данных) и один из них завершает работу раньше всех остальных. Тогда он может отменить их выполнение. Другим примером является обнаружение ошибки одним из одновременно выполняющих задачу потоков, который затем может отменить выполнение и всех остальных.
Для обработки отмены выполнения поток может установить (push) или снять (pop) обработчик-очиститель (cleanup handler):
#include <pthread.h>
void pthread_cleanup_push(void (*function) (void *) void *arg);
void pthread_cleanup_pop(int execute);
Эти обработчики представляют собой обычные функции, которые вызываются:
■ в случае отмены выполнения потока (другим потоком, вызвавшим pthread_ cancel);
■ в случае добровольного завершения работы (вызовом pthread_exit или выходом из начальной функции потока).
Обработчики-очистители выполняют всю необходимую работу по восстановлению значений переменных, такую как разблокирование взаимных исключений и семафоров, которые могли быть заблокированы данным потоком.
Аргумент function представляет собой адрес вызываемой функции, а arg — ее единственный аргумент. Функция pthread_cleanup_pop всегда удаляет обработчик из верхушки стека и вызывает эту функцию, если значение execute отлично от 0.
ПРИМЕЧАНИЕ
Мы снова встретимся с проблемой отмены выполнения потоков в связи с листингом 15.26, где может произойти отмена выполнения сервера с дверьми при завершении работы клиента в процессе обработки вызванной им процедуры.
Пример
Легче всего продемонстрировать проблему нашей реализации из предыдущего раздела с помощью примера. На рис. 8.1 изображена временная диаграмма выполнения нашей программы, а текст самой программы приведен в листинге 8.9.
Рис. 8.1. Временная диаграмма выполнения программы из листинга 8.9