Главная страница  Взаимодействие нетривиальных процессов 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 [ 65 ] 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186

Листинг 8.9 (продолжение)

37 void *

38 thread2(void *arg)

39 {

40 printf( thread2() trying to obtain a write lock\n );

41 Pthread rwlock wrlock(&rwlock);

42 printf( thread2() got a write lock\n ): /* не будет выполнено */

43 sleep(l):

44 Pthread rwlock umock(&rwlock);

45 return(NULL):

46 }

Функция threadl

26-36 Поток получает блокировку на чтение и ждет 3 секунды. Эта пауза дает возможность другому потоку вызвать pthread rwlock wrlock и заблокироваться при вызове pthread cond wait, поскольку блокировка на запись не может быть установлена из-за наличия блокировки на чтение. Затем первый поток вызывает pthread cancel для отмены выполнения второго потока, ждет 3 секунды, освобождает блокировку на чтение и завершает работу.

Функция thread2

37-46 Второй поток делает попытку получить блокировку на запись (которую он получить не может, поскольку первый поток получил блокировку на чтение). Оставшаяся часть функции никогда не будет выполнена.

При запуске этой программы с использованием функций из предыдущего раздела мы получим следующий результат:

Solaris % testcancel

threadlO got a read lock

thread2() trying to obtain a write lock

и мы никогда не вернемся к приглашению интерпретатора. Программа зависнет. Произошло вот что:

1. Второй поток вызвал pthread rwl ock wrl ock (листинг 8.6), которая была заблокирована в вызове pthread cond wait.

2. Первый поток вернулся из вызова sl еер(З) и вызвал pthread cancel.

3. Второй поток был отменен и завершил работу. При отмене потока, заблокированного в ожидании сигнала по условной переменной, взаимое исключение блокируется до вызова первого обработчика-очистителя. (Мы не устанавливали обработчик, но взаимное исключение все равно блокируется до завершения потока.) Следовательно, при отмене выполнения второго потока взаимное исключение осталось заблокированным и значение rw nwaitwriters в листинге 8.6 было увеличено.

4. Первый поток вызывает pthread rwlock unlock и блокируется навсегда при вызове pthread mutex lock (листинг 8.8), потому что взаимное исключение все еще заблокировано отмененным потоком.

Если мы уберем вызов pthread rwlock unlock в функции threadl, функция main

выведет вот что:

rw refcount - 1. rw nwaitreaders = О, rw nwaitwriters = 1 pthread rwlock destroy error: Device busy



Первый счетчик имеет значение 1, поскольку мы удалили вызов pthread rwl оск unlock, а последний счетчик имеет значение 1, поскольку он был увеличен вторым потоком до того, как тот был отменен.

Исправить эту проблему просто. Сначала добавим две строки к функции pthread rwlock rdlock в листинге 8.4. Строки отмечены знаком +:

rw->rw nwaitreaders++: + pthread cleanup push(rwiock cancelrdwait. (void*) rw):

result = pthread cond wait(&rw->rw condreaders. &rw->rw mutex): + pthread cieanup pop(0);

rw->rw nwaitreaders++;

Первая новая строка устанавливает обработчик-очиститель (функцию rwl ock cancel rdwai t), a его единственным аргументом является указатель rw. После возвращения из pthread cond wait вторая новая строка удаляет обработчик. Аргумент функции pthread cleanup pop означает, что функцию-обработчик при этом вызывать не следует. Если этот аргумент имеет ненулевое значение, обработчик будет сначала вызван, а затем удален.

Если поток будет отменен при вызове pthread cond wait, возврата из нее не произойдет. Вместо этого будут запущены обработчики (после блокирования соответствующего взаимного исключения, как мы отметили в пункте 3 чуть выше).

В листинге 8.10 приведен текст функции rwl ock cancel rdwai t, являющейся обработчиком-очистителем для phtread rwlock rdlock.

Листинг 8.10. Функция rwlock cancelrdwait: обработчик для блокировки чтения

my rwlock cancel/pthread rwlock rdlock.с

3 static void

4 rwlock cancelrdwait(void *arg)

6 pthread rwlock t *rw;

7 rw = arg;

8 rw->rw nwaitreaders--;

9 pthread mutex umock(&rw->rw mutex): 10 }

-9 Счетчик rw nwaitreaders уменьшается, a затем разблокируется взаимное исключение. Это состояние, которое должно быть восстановлено при отмене потока.

Аналогично мы исправим текст функции pthread rwl ock wrl ock из листинга 8.6. Сначала добавим две новые строки рядом с вызовом pthread cond wait:

rw->rw nwaitreaders++; + pthread cleanup push(rwlock cancerwrwait. (void *) rw);

result = pthread cond wait(&rw->rw condwriters. &rw->rw mutex); + pthread cieanup pop(0);

rw-> rw nwa itreaders--;

В листинге 8.11 приведен текст функции rwlock cancelwrwait, являющейся обработчиком-очистителем для запроса блокировки на запись.

Листинг 8.11. Функция rwlock cancelwrwait: обработчик для блокировки записи

my rwlock cancel/pthread rwlock wrlock.с

3 static void

4 rwlock cancelwrwait(void *arg) продолжение



Листинг 8.11 (продолжение)

6 pthread rwlock t *rw:

7 rw = arg;

8 rw->rw nwaitwr1ters--;

9 pthread mutex umock(&rw->rw mutex);

10 }

8-9 Счетчик rw nwai twri ters уменьшается, и взаимное исключение разблокируется. При запуске нашей тестовой программы из листинга 8.9 с этими новыми функциями мы получим правильные результаты;

Solaris % testcancel

threadlO got a read lock

thread2() trying to obtain a write lock

rw refcount = 0. rw nwaitreaders = 0. rw nwaitwriters = 0

Теперь три счетчика имеют правильные значения, первый поток возвращается из вызова pthread rwl ock un1 ock, а функция pthread rwlock destroy не возвращает ошибку EBUSY.

ПРИМЕЧАНИЕ

Этот раздел представляет собой обзор вопросов, связанных с отменой выполнения потоков. Для более детального изучения этих проблем можно обратиться, например, к разделу 5.3 книги [3].

8.6. Резюме

Блокировки чтения-записи позволяют лучше распараллелить работу с данными, чем обычные взаимные исключения, если защищаемые данные чаще считываются, чем изменяются. Функции для работы с этими блокировками определены стандартом Unix 98, их мы и описываем в этой главе. Аналогичные или подобные им функции должны появиться в новой версии стандарта Posix. По виду функции аналогичны функциям для работы со взаимными исключениями (глава 7).

Блокировки чтения-записи легко реализовать с использованием взаимных исключений и условных переменных. Мы приводим пример возможной реализации. В нашей версии приоритет имеют записывающие потоки, но в некоторых других версиях приоритет может быть отдан и считывающим потокам.

Потоки могут быть отменены в то время, когда они находятся в заблокированном состоянии, в частности при вызове pthread cond wait, и на примере нашей реализации мы убедились, что при этом могут возникнуть проблемы. Решить эту проблему можно путем использования обработчиков-очистителей.

Упражнения

1. Измените реализацию в разделе 8.4 таким образом, чтобы приоритет имели считывающие, а не записывающие потоки.

2. Сравните скорость работы нашей реализации из раздела 8.4 с предоставленной производителем.



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 [ 65 ] 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186

© 2000 - 2024 ULTRASONEX-AMFODENT.RU.
Копирование материалов разрешено исключительно при условии цититирования.