20.5. Ситуация гонок

20.5. Ситуация гонок

Ситуация гонок (race condition) обычно возникает, когда множество процессов получают доступ к общим для них данным, но корректность результата зависит от порядка выполнения процессов. Поскольку порядок выполнения процессов в типичных системах Unix зависит от множества факторов, которые могут меняться от запуска к запуску, иногда результат корректен, а иногда — нет. Наиболее сложным для отладки типом гонок является такой, когда результат получается некорректным только изредка. Более подробно о ситуациях гонок мы поговорим в главе 26, когда будем обсуждать взаимные исключения (mutex) и условные переменные (condition variables). При программировании потоков всегда возникают проблемы с ситуациями гонок, поскольку значительное количество данных является общим для всех потоков (например, все глобальные переменные).

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

Чтобы понять эту проблему, рассмотрим пример. Ситуация гонок возникает при выполнении программы из листинга 20.1. Потратьте несколько минут и посмотрите, сможете ли вы ее обнаружить. (Подсказка: в каком месте программы мы можем находиться, когда доставляется сигнал?) Вы можете также инициировать ситуацию гонок следующим образом: изменить аргумент функции alarm с 5 на 1 и добавить вызов sleep(1) сразу же после printf.

Когда мы после внесения этих изменений наберем первую строку ввода, эта строка будет отправлена как широковещательное сообщение, а мы установим аргумент функции alarm равным 1 с. Мы блокируемся в вызове функции recvfrom, а затем для нашего сокета приходит первый ответ, вероятно, в течение нескольких миллисекунд. Ответ возвращается функцией recvfrom, но затем мы входим в спящее состояние на одну секунду. Принимаются остальные ответы и помещаются в приемный буфер сокета. Но пока мы находимся в спящем состоянии, время таймера alarm истекает и генерируется сигнал SIGALRM. При этом вызывается наш обработчик сигнала, затем он возвращает управление и прерывает функцию sleep, в которой мы блокированы. Далее мы повторяем цикл и читаем установленные в очередь ответы с паузой в одну секунду каждый раз, когда выводится ответ. Прочитав все ответы, мы снова блокируемся в вызове функции recvfrom, однако таймер уже не работает. Мы окажемся навсегда заблокированы в вызове функции recvfrom. Фундаментальная проблема здесь в том, что наша цель — обеспечить прерывание блокирования в функции recvfrom обработчиком сигнала, однако сигнал может быть доставлен в любое время, и наша программа в момент доставки сигнала может находиться в любом месте бесконечного цикла for.

Теперь мы проанализируем четыре различных варианта решения этой проблемы: одно некорректное и три различных корректных решения.

Блокирование и разблокирование сигнала

Наше первое (некорректное) решение снижает вероятность появления ошибки, блокируя сигнал и предотвращая его доставку, пока наша программа выполняет оставшуюся часть цикла for. Эта версия представлена в листинге 20.2.

Листинг 20.2. Блокирование сигналов при выполнении в цикле for (некорректное решение)

//bcast/dgclibcast3.c

 1 #include "unp.h"

 2 static void recvfrom_alarm(int);

 3 void

 4 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)

 5 {

 6  int n;

 7  const int on = 1;

 8  char sendline[MAXLINE], recvline[MAXLINE + 1];

 9  sigset_t sigset_alrm;

10  socklen_t len;

11  struct sockaddr *preply_addr;

12  preply_addr = Malloc(servlen);

13  Setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));

14  Sigemptyset(&sigset_alrm);

15  Sigaddset(&sigset_alrm, SIGALRM);

16  Signal(SIGALRM, recvfrom_alarm);

17  while (Fgets(sendline, MAXLINE, fp) != NULL) {

18   Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

19   alarm(5);

20   for (;;) {

21    len = servlen;

22    Sigprocmask(SIG_UNBLOCK, &sigset_alrm, NULL);

23    n = recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);

24    Sigprocmask(SIG_BLOCK, &sigset_alrm, NULL);

25    if (n < 0) {

26     if (errno == EINTR)

27      break; /* окончание ожидания ответа */

28     else

29      err_sys("recvfrom error");

30    } else {

31     recvline[n] = 0; /* завершающий нуль */

32     printf("from %s: %s",

33     Sock_ntop_host(preply_addr, len), recvline);

34    }

35   }

36  }

37  free(preply_addr);

38 }

39 static void

40 recvfrom_alarm(int signo)

41 {

42  return; /* выход из recvfrom() */

43 }

Объявление набора сигналов и инициализация

14-15 Мы объявляем набор сигналов, инициализируем его как пустой набор (sigemptyset) и включаем бит, соответствующий сигналу SIGALRM (sigaddset).

Разблокирование и блокирование сигнала

21-24 Перед вызовом функции recvfrom мы разблокируем сигнал (с тем, чтобы он мог быть доставлен, пока наша программа блокирована), а затем блокируем его, как только завершается функция recvfrom. Если сигнал генерируется (истекает время таймера), когда сигнал блокирован, то ядро запоминает этот факт, но доставить сигнал (то есть вызвать наш обработчик) не может, пока сигнал не будет разблокирован. В этом состоит принципиальная разница между генерацией сигнала и его доставкой. В главе 10 [110] предоставлена более подробная информация обо всех аспектах обработки сигналов POSIX.

Если мы откомпилируем и запустим эту программу, нам будет казаться, что она работает нормально, но все программы, порождающие ситуацию гонок, большую часть времени работают без каких-либо проблем! Проблема остается: разблокирование сигнала, вызов функции recvfrom и блокирование сигнала — все эти действия являются независимыми системными вызовами. Будем считать, что функция recvfrom возвращает последний ответ на нашу дейтаграмму, а сигнал доставляется между вызовом функции recvfrom и блокированием сигнала. Следующий вызов функции recvfrom заблокируется навсегда. Мы ограничили размер окна, но проблема осталась.

Вариантом решения может быть установка глобального флага при доставке сигнала его обработчиком:

recvfrom_alarm(int signo) {

 had_alarm = 1;

 return;

}

Флаг сбрасывается в 0 каждый раз, когда вызывается функция alarm. Наша функция dg_cli проверяет этот флаг перед вызовом функции recvfrom и не вызывает ее, если флаг ненулевой.

for (;;) {

 len = servlen;

 Sigprocmask(SIG_UNBLOCK, &sigset_alrm, NULL);

 if (had_alarm == 1)

  break;

 n = recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);

Если сигнал был сгенерирован во время его блокирования (после предыдущего возвращения из функции recvfrom), то после разблокирования в этой части кода он будет доставлен перед завершением функции sigprocmask, устанавливающей наш флаг. Однако между проверкой флага и вызовом функции recvfrom существует промежуток времени, в течение которого сигнал может быть сгенерирован и доставлен, и если это произойдет, вызов функции recvfrom заблокируется навсегда (разумеется, мы считаем при этом, что не приходит никаких дополнительных ответов).

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

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

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

АНАЛИЗЫ: Патовая патентная ситуация

Из книги Журнал «Компьютерра» №29 от 16 августа 2005 года автора Журнал «Компьютерра»

АНАЛИЗЫ: Патовая патентная ситуация 6 июля сего года Европарламент 648 голосами против 14 во втором чтении отклонил направленный на утверждение Евросоветом проект «Директивы о патентовании компьютерно-реализованных изобретений», ставшей за последний год более известной


Нереволюционная ситуация

Из книги Журнал «Компьютерра» №35 от 28 сентября 2005 года автора Журнал «Компьютерра»

Нереволюционная ситуация В этом году самое главное игровое мероприятие осени, Tokyo Game Show, посетило рекордное количество любителей игр и интерактивных развлечений — более 176 тысяч человек. По данным организатора выставки, ассоциации Computer Entertainment Supplier’s Association, прежний


Реальная ситуация

Из книги Разгони свой сайт автора Мациевский Николай

Реальная ситуация Рис. 5.7. Диаграмма загрузки (неизмененного) сайта


Бизнес-ситуация 1.2: проектирование таблиц и отношений

Из книги Обработка баз данных на Visual Basic®.NET автора Мак-Манус Джеффри П

Бизнес-ситуация 1.2: проектирование таблиц и отношений Брэд Джонс понял, что компании Jones Novelties Incorporated необходим способ сохранения информации о клиентах. Он совершенно уверен в том, что большинство его деловых контактов не будут однократными, и поэтому хочет иметь


10.4.5. Состояния гонок и sig_atomic_t (ISO C)

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

10.4.5. Состояния гонок и sig_atomic_t (ISO C) Пока обработка одного сигнала за раз выглядит просто: установка обработчика сигнала в main() и (не обязательная) переустановка самого себя обработчиком сигнала (или установка действия SIG_IGN) в качестве первого действия обработчика.Но что


3. Проблемная ситуация, которую вы преодолели

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

3. Проблемная ситуация, которую вы преодолели Наверняка у каждого в жизни были ситуации, когда вы чего-то боялись или испытывали дискомфорт. Например, во время публичных выступлений или при общении с противоположным полом, на работе. У вас были проблемы, но вы их


Ситуация 4. Авария загрузочного диска

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

Ситуация 4. Авария загрузочного диска Технический бандит. Чинит диск. Обычно ему удается восстановить файловую систему прямо из приглашения загрузки. Если это не помогает, запускает микроядро, которое запускает на соседнем компьютере скрипт, копирующий на аварийную


Ситуация 5. Слабая производительность сети

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

Ситуация 5. Слабая производительность сети Технический бандит. Пишет скрипт для мониторинга сети, переписывает программное обеспечение, чем повышает производительность на 2%. Пожимает плечами, говорит: "Я сделал все, что мог", и отправляется в поход в


Ситуация 7. Установка новой версии операционной системы

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

Ситуация 7. Установка новой версии операционной системы Технический бандит. Изучает исходные тексты новой версии и выбирает из них только то, что ему нравится.Администратор-фашист. В первую очередь изучает законодательные акты против производителя, поставляющего