UNIX: разработка сетевых приложений
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(допустим, новый дескриптор равен 5) и в главном потоке вызывается функцияconnfd. Указатель наpthread_create(а не фактическое его значение!) является последним аргументом функцииconnfd.pthread_create■ Создается новый поток, и начинает выполняться функция
.doit■ Готово другое соединение, и главный поток снова начинает выполняться (прежде, чем начнется выполнение вновь созданного потока). Завершается функция
, записывается новое значение переменнойaccept(например, значение нового дескриптора равно 6) и главный поток вновь вызывает функциюconnfd.pthread_createХотя созданы два новых потока, оба они будут работать с одним и тем же последним значением переменной
, которое, согласно нашему предположению, равно 6. Проблема заключается в том, что несколько потоков получают доступ к совместно используемой переменной (целочисленному значению, хранящемуся вconnfd) при отсутствии синхронизации. В листинге 26.2 мы решаем эту проблему, передавая значение переменнойconnfdфункцииconnfd, вместо того чтобы передавать указатель на это значение. Этот метод работает благодаря тому способу, которым целочисленные значения в С передаются вызываемой функции (копия значения помещается в стек вызванной функции).pthread_createВ листинге 26.3 показано более удачное решение описанной проблемы.
Листинг 26.3. Эхо-сервер TCP, использующий потоки с более переносимой передачей аргументов
//threads/tcpserv02.c1 #include "unpthread.h"2 static void *doit(void*); /* каждый поток выполняет эту функцию */3 int4 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 else15 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не допускали повторного вхождения. Это означает, что при вызове той или иной функции из обработчика сигнала в то время, когда главный поток выполняет одну из них, возникает большая путаница, так как эти функции оперируют статическими структурами данных. Как же мы можем вызывать эти две функции в листинге 26.3? Дело в том, что в POSIX требуется, чтобы эти две функции, так же как и многие другие, были безопасными в многопоточной среде (thread-safe). Обычно это достигается с помощью некоторой разновидности синхронизации, осуществляемой внутри библиотечных функций и являющейся для нас прозрачной (то есть незаметной).free