61 void*
62 do_get_read(void *vptr)
63 {
64 int fd, n;
65 char line[MAXLINE];
66 struct file *fptr;
67 fptr = (struct file*)vptr;
68 fd = Tcp_connect(fptr-f_host, SERV);
69 fptr-f_fd = fd;
70 printf("do_get_read for %s, fd %d, thread %d\n",
71 fptr-f_name, fd, fptr-f_tid);
72 write_get_cmd(fptr);
73 /* Чтение ответа сервера */
74 for (;;) {
75 if ((n = Read(fd, line, MAXLINE)) == 0)
76 break; /* сервер закрывает соединение */
77 printf ("read %d bytes from %s\n", n, fptr-f_name);
78 }
79 printf("end-of-file on %s\n\", fptr-f_name);
80 Close(fd);
81 fptr-f_flags = F_DONE; /* сбрасываем F_READING */
82 return (fptr); /* завершение потока */
83 }
68-71
tcp_connect
устанавливается соединение. В данном случае используется обычный блокируемый сокет, поэтому поток будет блокирован при вызове функции
connect
, пока не будет установлено соединение.72
write_get_cmd
формирует команду HTTP
GET
и отсылает ее серверу. Мы не показываем эту функцию заново, так как единственным отличием от листинга 16.12 является то, что в версии, использующей потоки, не вызывается макрос
FD_SET
и не используется
maxfd
.73-82
F_DONE
и функция возвращает управление, завершая выполнение потока.Мы также не показываем функцию
home_page
, так как она полностью повторяет версию, приведенную в листинге 16.10.Мы вернемся к этому примеру, заменив функцию Solaris
thr_join
на более переносимую функцию семейства Pthreads, но сначала нам необходимо обсудить взаимные исключения и условные переменные.26.7. Взаимные исключения
Обратите внимание на то, что в листинге 26.8 при завершении выполнения очередного потока в главном цикле уменьшаются на единицу и
nconn
, и
nlefttoread
. Мы могли бы поместить оба эти оператора уменьшения в одну функцию
do_get_read
, что позволило бы каждому потоку уменьшать эти счетчики непосредственно перед тем, как выполнение потока завершается. Но это привело бы к возникновению трудноуловимой серьезной ошибки параллельного программирования.Проблема, возникающая при помещении определенного кода в функцию, которая выполняется каждым потоком, заключается в том, что обе эти переменные являются глобальными, а не собственными переменными потока. Если один поток в данный момент уменьшает значение переменной и это действие приостанавливается, чтобы выполнился другой поток, который также станет уменьшать на единицу эту переменную, может произойти ошибка. Предположим, например, что компилятор С осуществляет уменьшение переменной на единицу в три этапа: загружает информацию из памяти в регистр, уменьшает значение регистра, а затем сохраняет значение регистра в памяти. Рассмотрим возможный сценарий.
1. Выполняется поток А, который загружает в регистр значение переменной
nconn
(равное 3).2. Система переключается с выполнения потока А на выполнение потока В. Регистры потока А сохранены, регистры потока В восстановлены.
3. Поток В выполняет три действия, составляющие оператор декремента в языке С (
nconn--
), сохраняя новое значение переменной
nconn
, равное 2.4. Впоследствии в некоторый момент времени система переключается на выполнение потока А. Восстанавливаются регистры потока А, и он продолжает выполняться с того места, на котором остановился, а именно начиная со второго этапа из трех, составляющих оператор декремента. Значение регистра уменьшается с 3 до 2, и значение 2 записывается в переменную
nconn
.Окончательный результат таков: значение
nconn
равно 2, в то время как оно должно быть равным 1. Это ошибка.