Шрифт:
}
С точки зрения 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
.