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

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

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

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 194 195 196 197 198 199 200 201 202 ... 263
Перейти на страницу:

/* Ждем завершения выполнения одного или нескольких потоков */

while (ndone == 0)

 Pthread_cond_wait(&ndone_cond, &ndone_mutex);

Но при этом существует вероятность, что последний поток увеличивает значение переменной ndone после проверки главным потоком условия ndone == 0, но перед вызовом функции pthread_cond_wait. Если это происходит, то последний «сигнал» теряется, и основной цикл оказывается заблокированным навсегда, так как он будет ждать события, которое никогда не произойдет.

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

/* Ждем завершения выполнения одного или нескольких потоков */

Pthread_mutex_lock(&ndone_mutex);

while (ndone == 0) {

 Pthread_mutex_unlock(&ndone_mutex);

 Pthread_cond_wait(&ndone_cond, &ndone_mutex);

 Pthread_mutex_lock(&ndone_mutex);

}

Существует вероятность того, что по завершении выполнения поток увеличит на единицу значение переменной ndone и это произойдет между вызовом функций pthread_mutex_unlock и pthread_cond_wait.

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

#include <pthread.h>

int pthread_cond_broadcast(pthread_cond_t *cptr);

int pthread_cond_timedwait(pthread_cond_t *cptr, pthread_mutex_t *mptr,

 const struct timespec *abstime);

Обе функции возвращают: 0 в случае успешного выполнения, положительное значение Exxx в случае ошибки

Функция pthread_cond_timedwait позволяет потоку задать предельное время блокирования. Аргумент abstime представляет собой структуру timespec (определенную в разделе 6.9 при рассмотрении функции pselect), которая задает системное время для момента, когда функция должна возвратить управление, даже если к этому моменту условная переменная не подала сигнал. Если возникает такая ситуация, возвращается ошибка ETIME.

В данном случае значение времени является абсолютным значением времени, в отличие от относительного значения разницы во времени (time delta) между некоторыми событиями. Иными словами, abstime — это системное время, то есть количество секунд и наносекунд, прошедших с 1 января 1970 года (UTC) до того момента, когда эта функция должна вернуть управление. Здесь имеется различие как с функцией pselect, так и с функцией select, задающими количество секунд (и наносекунд в случае pselect) до некоторого момента в будущем, когда функция должна вернуть управление. Обычно для этого вызывается функция gettimeofday, которая выдает текущее время (в виде структуры timeval), а затем оно копируется в структуру timespec и к нему добавляется требуемое значение:

struct timeval tv;

struct timespec ts;

if (gettimeofday(&tv, NULL) < 0)

 err_sys("gettimeofday error");

ts.tv_sec = tv.tv_sec + 5; /* 5 с в будущем */

ts.tv_nsec = tv.tv_usec * 1000; /* микросекунды переводим в наносекунды */

pthread_cond_timedwait( , &ts);

Преимущество использования абсолютного времени (в противоположность относительному) заключается в том, что функция может завершиться раньше (возможно, из-за перехваченного сигнала). Тогда функцию можно вызвать снова, не меняя содержимое структуры timespec. Недостаток этого способа заключается в необходимости вызывать дополнительно функцию gettimeofday перед тем, как в первый раз вызывать функцию pthread_cond_timedwait.

ПРИМЕЧАНИЕ

В POSIX определена новая функция clock_gettime, возвращающая текущее время в виде структуры timespec.

26.9. Веб-клиент и одновременный доступ

Изменим код нашего веб-клиента из раздела 26.6: уберем вызов функции Solaris thr_join и заменим его вызовом функции pthread_join. Как сказано в разделе 26.6, теперь нам нужно точно указать, завершения какого потока мы ждем. Для этого мы используем условную переменную, описанную в разделе 26.8.

Единственным изменением в отношении глобальных переменных (см. листинг 26.7) является добавление нового флага и условной переменной:

#define F_JOINED 8 /* количество потоков */

int ndone; /* количество завершившихся потоков */

pthread_mutex_t ndone_mutex = PTHREAD_MUTEX_INITIALIZER;

pthread_cond_t ndone_cond = PTHREAD_COND_IINITIALIZER;

Единственным изменением функции do_get_read (см. листинг 26.9) будет увеличение на единицу значения переменной ndone и оповещение главного цикла о завершении выполнения потока:

 printf("end-of-file on %sn", fptr->f_name);

 Close(fd);

 Pthread_mutex_lock(&ndone_mutex);

 fptr->f_flags = F_DONE; /* сбрасывает флаг F_READING */

 ndone++;

 Pthread_cond_signal(&ndone_cond);

 Pthread_mutex_unlock(&ndone_mutex);

 return(fptr); /* завершение выполнения потока */

}

Большинство изменений касается главного цикла, представленного в листинге 26.8. Новая версия показана в листинге 26.13.

Листинг 26.13. Основной рабочий цикл функции main

//threads/web03.c

43  while (nlefttoread > 0) {

44   while (nconn < maxnconn && nlefttoconn > 0) {

45    /* находим файл для считывания */

46    for (i = 0; i < nfiles; i++)

47     if (file[i].f_flags == 0)

48      break;

49    if (i == nfiles)

50     err_quit("nlefttoconn = %d but nothing found", nlefttoconn);

51    file[i].f_flags = F_CONNECTING;

52    Pthread_create(&tid, NULL, &do_get_read, &file[i]);

53    file[i].f_tid = tid;

54    nconn++;

55    nlefttoconn--;

56   }

57   /* Ждем завершения выполнения одного из потоков */

58   Pthread_mutex_lock(&ndone_mutex);

59   while (ndone == 0)

60    Pthread_cond_wait(&ndone_cond, &ndone_mutex);

61   for (i = 0; i < nfiles; i++) {

62    if (file[i].f_flags & F_DONE) {

63     Pthread_join(file[i].f_tid, (void**)&fptr);

64     if (&file[i] != fptr)

65      err_quit("file[i] != fptr");

66     fptr->f_flags = F_JOINED; /* clears F_DONE */

67     ndone--;

68     nconn--;

69     nlefttoread--;

70     printf("thread %d for %s donen", fptr->f_tid, fptr->f_name);

71    }

72   }

73   Pthread_mutex_unlock(&ndone_mutex);

74  }

75  exit(0);

76 }

По возможности создаем новый поток

44-56 Эта часть кода не изменилась.

Ждем завершения выполнения потока

57-60 Мы ждем завершения выполнения потоков, отслеживая, когда значение ndone станет равно нулю. Как сказано в разделе 26.8, эта проверка должна быть проведена перед тем, как взаимное исключение будет блокировано, а переход потока в состояние ожидания осуществляется функцией pthread_cond_wait.

Обработка завершенного потока

61-73 Когда выполнение потока завершилось, мы перебираем все структуры file, отыскивая соответствующий поток, вызываем pthread_join, а затем устанавливаем новый флаг F_JOINED.

В табл. 16.1 показано, сколько времени требует выполнение этой версии веб-клиента, а также версии, использующей неблокируемую функцию connect.

26.10. Резюме

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

Все потоки одного процесса совместно используют глобальные переменные и дескрипторы, тем самым эта информация становится доступной всем потокам процесса. Но совместное использование информации вносит проблемы, связанные с синхронизацией доступа к разделяемым переменным, и поэтому нам следует использовать примитивы синхронизации технологии Pthreads — взаимные исключения и условные переменные. Синхронизация доступа к совместно используемым данным — необходимое условие почти для любого приложения, работающего с потоками.

1 ... 194 195 196 197 198 199 200 201 202 ... 263
Перейти на страницу:
Тут вы можете бесплатно читать книгу UNIX: разработка сетевых приложений - Уильям Стивенс.
Комментарии