KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программное обеспечение » Уильям Стивенс - UNIX: разработка сетевых приложений

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

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Уильям Стивенс, "UNIX: разработка сетевых приложений" бесплатно, без регистрации.
Перейти на страницу:

В этой программе имеется одна неявная ошибка, о которой рассказывается в разделе 26.5. Можете ли вы ее обнаружить? (См. упражнение 26.5.)

Передача аргументов новым потокам

Мы уже упомянули, что в листинге 26.2 мы преобразуем целочисленную переменную connfd к указателю на неопределенный тип (void), но этот способ не работает в некоторых системах. Для корректной обработки данной ситуации требуются дополнительные усилия.

В первую очередь, заметим, что мы не можем просто передать адрес connfd нового потока, то есть следующий код не будет работать:

int main(int argc, char **argv) {

 int listenfd, connfd;

 ...


 for (;;) {

  len = addrlen;

  connfd = Accept(listenfd, cliaddr, &len);


  Pthread_create(&tid, NULL, &doit, &connfd);

 }

}


static void* doit(void *arg) {

 int connfd;


 connfd = *((int*)arg);

 Pthread_detach(pthread_self());

 str_echo(connfd); /* та же функция, что и прежде */

 Close(connfd);    /* мы закончили с присоединенным сокетом */

 return(NULL);

}

С точки зрения ANSI С здесь все в порядке: мы гарантированно можем преобразовать целочисленный указатель к типу void* и затем обратно преобразовать получившийся указатель на неопределенный тип к целочисленному указателю. Проблема заключается в другом — на что именно он будет указывать?

В главном потоке имеется одна целочисленная переменная connfd, и при каждом вызове функции accept значение этой переменной меняется на новое (в соответствии с новым присоединенным сокетом). Может сложиться следующая ситуация:

■ Функция accept возвращает управление, записывается новое значение переменной connfd (допустим, новый дескриптор равен 5) и в главном потоке вызывается функция pthread_create. Указатель на connfd (а не фактическое его значение!) является последним аргументом функции pthread_create.

■ Создается новый поток, и начинает выполняться функция doit.

■ Готово другое соединение, и главный поток снова начинает выполняться (прежде, чем начнется выполнение вновь созданного потока). Завершается функция accept, записывается новое значение переменной connfd (например, значение нового дескриптора равно 6) и главный поток вновь вызывает функцию pthread_create.

Хотя созданы два новых потока, оба они будут работать с одним и тем же последним значением переменной connfd, которое, согласно нашему предположению, равно 6. Проблема заключается в том, что несколько потоков получают доступ к совместно используемой переменной (целочисленному значению, хранящемуся в connfd) при отсутствии синхронизации. В листинге 26.2 мы решаем эту проблему, передавая значение переменной connfd функции pthread_create, вместо того чтобы передавать указатель на это значение. Этот метод работает благодаря тому способу, которым целочисленные значения в С передаются вызываемой функции (копия значения помещается в стек вызванной функции).

В листинге 26.3 показано более удачное решение описанной проблемы.

Листинг 26.3. Эхо-сервер TCP, использующий потоки с более переносимой передачей аргументов

//threads/tcpserv02.c

 1 #include "unpthread.h"


 2 static void *doit(void*); /* каждый поток выполняет эту функцию */


 3 int

 4 main(int argc, char **argv)

 5 {

 6  int listenfd, *iptr;

 7  thread_t tid;

 8  socklen_t addrlen, len;

 9  struct sockaddr *cliaddr;


10  if (argc == 2)

11   listenfd = Tcp_listen(NULL, argv[1], &addrlen);

12  else if (argc == 3)

13   listenfd = Tcp_listen(argv[1], argv[2], &addrlen);

14  else

15   err_quit("usage: tcpserv01 [ <host> ] <service or port>");


16  cliaddr = Malloc(addrlen);


17  for (;;) {

18   len = addrlen;

19   iptr = Malloc(sizeof(int));

20   *iptr = Accept(listenfd, cliaddr, &len);

21   Pthread_create(&tid, NULL, &doit, iptr);

22  }

23 }


24 static void*

25 doit(void *arg)

26 {

27  int connfd;


28  connfd = *((int*)arg);

29  free(arg);


30  Pthread_detach(pthread_self());

31  str_echo(connfd); /* та же функция, что и раньше */

32  Close(connfd); /* мы закончили с присоединенным сокетом */

33  return (NULL);

34 }

17-22 Каждый раз перед вызовом функции accept мы вызываем функцию malloc и выделяем в памяти пространство для целочисленной переменной (дескриптора присоединенного сокета). Таким образом каждый поток получает свою собственную копию этого дескриптора.

28-29 Поток получает значение дескриптора присоединенного сокета, а затем освобождает занимаемую им память с помощью функции free.

Исторически функции malloc и free не допускали повторного вхождения. Это означает, что при вызове той или иной функции из обработчика сигнала в то время, когда главный поток выполняет одну из них, возникает большая путаница, так как эти функции оперируют статическими структурами данных. Как же мы можем вызывать эти две функции в листинге 26.3? Дело в том, что в POSIX требуется, чтобы эти две функции, так же как и многие другие, были безопасными в многопоточной среде (thread-safe). Обычно это достигается с помощью некоторой разновидности синхронизации, осуществляемой внутри библиотечных функций и являющейся для нас прозрачной (то есть незаметной).

Функции, безопасные в многопоточной среде

Стандарт POSIX.1 требует, чтобы все определенные в нем функции, а также функции, определенные в стандарте ANSI С, были безопасными в многопоточной среде. Исключения из этого правила приведены в табл. 26.1.

К сожалению, в POSIX.1 ничего не сказано о безопасности в многопоточной среде по отношению к функциям сетевого API. Последние пять строк в этой таблице появились благодаря Unix 98. В разделе 11.18 мы говорили о том, что функции gethostbyname и gethostbyaddr не допускают повторного вхождения. Как уже отмечалось, некоторые производители определяют версии этих функций, обладающие свойством безопасности в многопоточной среде (их названия заканчиваются на _r), но поскольку они не стандартизованы, лучше от них отказаться. Все функции getXXX, не допускающие повторного вхождения, были приведены в табл. 11.5.


Таблица 26.1. Функции, безопасные в многопоточной среде

Могут не быть безопасными в многопоточной среде Должны быть безопасными в многопоточной среде Комментарии Asctime asctime_r Безопасна в многопоточной среде только в случае непустого аргумента ctermid Ctime ctime_r getc_unlocked getchar_unlocked Getgrid getgrid_r Getgrnam getgrnam_r Getlogin getlogin_r Getpwnam getpwnam_r Getpwuid getpwuid_r Gmtime gmtime_r Localtime localtime_r putc_unlocked putchar_unlocked Rand rand_r Readdir readdir_r Strtock strtock_r tmpnam Безопасна в многопоточной среде только в случае непустого аргумента Ttyname ttyname_r GethostXXX GetnetXXX GetprotoXXX GetservXXX inet_ntoa

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

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*