Пример

Пример

Легче всего продемонстрировать проблему нашей реализации из предыдущего раздела с помощью примера. На рис. 8.1 изображена временная диаграмма выполнения нашей программы, а текст самой программы приведен в листинге 8.9. 

Рис. 8.1. Временная диаграмма выполнения программы из листинга 8.9

Создание двух потоков

10-13 Создаются два потока, первый из которых выполняет функцию thread1, а второй — thread2. После создания первого делается пауза длительностью в одну секунду, чтобы он успел заблокировать ресурс на чтение. 

Ожидание завершения потоков

14-23 Мы ожидаем завершения работы второго потока и проверяем, что его статус имеет значение PTHREAD_CANCEL. Затем мы ждем завершения работы первого потока и проверяем, что его статус представляет собой нулевой указатель. Затем мы выводим значение трех счетчиков в структуре pthread_rwlock_t и уничтожаем блокировку.

Листинг 8.9. Тестовая программа, иллюстрирующая отмену выполнения потока

//my_rwlock_cancel/testcancel.с

1  #include "unpipc.h"

2  #include "pthread_rwlock.h"

3  pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;

4  pthread_t tid1, tid2;

5  void *thread1(void *), *thread2(void *);

6  int

7  main(int argc, char **argv)

8  {

9   void *status;

10  Set_concurrency(2);

11  Pthread_create(&tid1, NULL, thread1, NULL);

12  sleep(1); /* даем первому потоку возможность получить блокировку */

13  Pthread_create(&tid2, NULL, thread2, NULL);

14  Pthread_join(tid2, &status);

15  if (status != PTHREAD_CANCELED)

16   printf("thread2 status = %p ", status);

17  Pthread_join(tid1, &status);

18  if (status != NULL)

19   printf("thread1 status = %p ", status);

20  printf("rw_refcount = %d, rw_nwaitreaders = %d, rw_nwaitwriters = %d ",

21   rwlock.rw_refcount, rwlock.rw_nwaitreaders,

22   rwlock.rw_nwaitwriters);

23  Pthread_rwlock_destroy(&rwlock);

24  exit(0);

25 }

26 void *

27 thread1(void *arg)

28 {

29  Pthread_rwlock_rdlock(&rwlock);

30  printf("thread1() got a read lock ");

31  sleep(3); /* даем второму потоку возможность заблокироваться при вызове pthread_rwlock_wrlock() */

32  pthread_cancel(tid2);

33  sleep(3);

34  Pthread_rwlock_unlock(&rwlock);

35  return(NULL);

36 }

37 void *

38 thread2(void *arg)

39 {

40  printf("thread2() trying to obtain a write lock "):

41  Pthread_rwlock_wrlock(&rwlock);

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

43  sleep(1);

44  Pthread_rwlock_unlock(&rwlock);

45  return(NULL);

46 }

Функция thread1

26-36 Поток получает блокировку на чтение и ждет 3 секунды. Эта пауза дает возможность другому потоку вызвать pthread_rwlock_wrlock и заблокироваться при вызове pthread_cond_wait, поскольку блокировка на запись не может быть установлена из-за наличия блокировки на чтение. Затем первый поток вызывает pthread_cancel для отмены выполнения второго потока, ждет 3 секунды, освобождает блокировку на чтение и завершает работу.

Функция thread2

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

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

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.

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

//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, являющейся обработчиком-очистителем для запроса блокировки на запись.

Листинг 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

rw_refcount = 0, rw_nwaitreaders = 0, rw_nwaitwriters = 0

Теперь три счетчика имеют правильные значения, первый поток возвращается из вызова pthread_rwlock_unlock, а функция pthread_rwlock_destroy не возвращает ошибку EBUSY.

ПРИМЕЧАНИЕ

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

Поделитесь на страничке

Следующая глава >

Похожие главы из других книг

Пример

Из книги Обработка событий в С++ автора Клюев Александр

Пример В примере создаются два класса обработчик и инициатор события, устанавливается связь между ними и иллюстрируется обработка события в нескольких объектах одновременно:#include "stdafx.h" #include "sigslot.h"struct EventRaiser { // источник события signal<const char*> event; // const char* – тип аргумента.


25.5. Пример

Из книги Разработка приложений в среде Linux. Второе издание автора Джонсон Майкл К.

25.5. Пример Для закрепления материала этой главы ниже приводится пример приложения, в котором задействовано большинство функциональных возможностей qdbm. Подразумевается, что в результате выполнения этого приложения будет создана простая база данных телефонных номеров,


27.1.1. Пример

Из книги Искусство программирования на языке сценариев командной оболочки автора Купер Мендель

27.1.1. Пример В главе 8 был представлен пример использования обычной разделяемой библиотеки. Библиотеку libhello.so, которую нам удалось создать, можно загружать во время выполнения. Программа loadhello загружает libhello.so динамически и вызывает функцию print_hello, которая находится в


Пример 10-27. Простой пример сравнения строк

Из книги Программирование для Linux. Профессиональный подход автора Митчелл Марк

Пример 10-27. Простой пример сравнения строк #!/bin/bash# match-string.sh: простое сравнение строкmatch_string (){ MATCH=0 NOMATCH=90 PARAMS=2 # Функция требует два входных аргумента. BAD_PARAMS=91 [ $# -eq $PARAMS ] || return $BAD_PARAMS case "$1" in "$2") return $MATCH;; * ) return $NOMATCH;; esac}a=oneb=twoc=threed=twomatch_string $a # неверное число


Пример 12-20. Пример форматирования списка файлов в каталоге

Из книги UNIX: разработка сетевых приложений автора Стивенс Уильям Ричард

Пример 12-20. Пример форматирования списка файлов в каталоге #!/bin/bash# За основу сценария взят пример "man column".(printf "PERMISSIONS LINKS OWNER GROUP SIZE DATE TIME PROG-NAME " ; ls -l | sed 1d) | column -t# Команда "sed 1d" удаляет первую строку, выводимую командой ls,#+ (для локали "С" это строка: "total N",#+ где "N" -- общее


Пример 12-45. Пример работы с m4

Из книги UNIX — универсальная среда программирования автора Пайк Роб

Пример 12-45. Пример работы с m4 #!/bin/bash# m4.sh: Демонстрация некоторых возможносией макропроцессора m4# Строкиstring=abcdA01echo "len($string)" | m4 # 7echo "substr($string,4)" | m4 # A01echo "regexp($string,[0-1][0-1],&Z)" | m4 # 01Z# Арифметикаecho "incr(22)" | m4 # 23echo "eval(99 / 3)" | m4 #


Пример 24-2. Еще один пример проверки аргументов с помощью "И-списков"

Из книги автора

Пример 24-2. Еще один пример проверки аргументов с помощью "И-списков" #!/bin/bashARGS=1 # Ожидаемое число аргументов.E_BADARGS=65 # Код завершения, если число аргументов меньше ожидаемого.test $# -ne $ARGS && echo "Порядок использования: `basename $0` $ARGS аргумент(а)(ов)" && exit $E_BADARGS# Если


Пример 25-8. Пример реализации алгоритма Решето Эратосфена

Из книги автора

Пример 25-8. Пример реализации алгоритма Решето Эратосфена #!/bin/bash# sieve.sh# Решето Эратосфена# Очень старый алгоритм поиска простых чисел.# Этот сценарий выполняется во много раз медленнее# чем аналогичная программа на C.LOWER_LIMIT=1 # Начиная с 1.UPPER_LIMIT=1000 # До 1000.# (Вы можете


9.4. Пример

Из книги автора

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


Пример

Из книги автора

Пример В листинге 11.1[1] показана простая программа, вызывающая функцию gethostbyname для любого числа аргументов командной строки и выводящая всю возвращаемую информацию.Листинг 11.1. Вызов функции и вывод возвращаемой информации//names/hostent.c 1 #include "unp.h" 2 int 3 main(int argc, char **argv) 4 { 5  char


Пример

Из книги автора

Пример Сначала приведем пример с Ipv4:freebsd % traceroute www.unpbook.comtraceroute to www.unpbook.com (206.168.112.219): 30 hops max. 24 data bytes1 12.106.32.1 (12.106.32.1) 0.799 ms 0.719 ms 0.540 ms2 12.124.47.113 (12.124.47.113) 1.758 ms 1.760 ms 1.839 ms3 gbr2-p27.sffca.ip.att.net (12.123.195.38) 2.744 ms 2.575 ms 2.648 ms4 tbr2-p012701.sffca.ip.att.net (12.122.11.85) 3.770 ms 3.689 ms 3.848 ms5 gbr3-p50.dvmco.ip.att.net (12.122.2.66) 26.202 ms 26.242 ms


Пример

Из книги автора

Пример Сначала мы запустим нашу программу с аргументом командной строки -0 и убедимся, что сервер имен отвечает на приходящие дейтаграммы, не содержащие контрольной суммы. Мы также задаем флаг -v.macosx # udpcksum -i en1 -0 -v bridget.rudoff.com domaindevice = en1local net = 172.24.37.64. netmask = 255.255.255.224cmd = udp and src host