Затем мы требуем, чтобы каждый поток по завершении своего выполнения увеличивал этот счетчик на единицу, используя соответствующее взаимное исключение.
void* do_get_read(void *vptr) {
...
Pthread_mutex_lock(&ndone_mutex);
ndone++;
Pthread_mutex_unlock(&ndone_mutex);
return(fptr); /*
завершение выполнения потока */
}
Но каким при этом получается основной цикл? Взаимное исключение должно быть постоянно блокировано основным циклом, который проверяет, какие потоки завершили свое выполнение.
while (nlefttoread > 0) {
while (nconn < maxnconn && nlefttoconn > 0) {
/* находим файл для чтения */
...
}
/* Проверяем, не завершен ли поток */
Pthread_mutex_lock(&ndone_mutex);
if (ndone > 0) {
for (i =0; i < nfiles; i++) {
if (file[i].f_flags & F_DONE) {
Pthread_join(file[i].f_tid, (void**)&fptr);
/* обновляем file[i] для завершенного потока */
...
}
}
}
Pthread_mutex_unlock(&ndone_mutex);
}
Это означает, что главный поток никогдане переходит в спящее состояние, а просто входит в цикл, проверяя каждый раз значение переменной
ndone
. Этот процесс называется опросом( polling) и рассматривается как пустая трата времени центрального процессора.
Нам нужен метод, с помощью которого главный цикл мог бы входить в состояние ожидания, пока один из потоков не оповестит его о том, что какая-либо задача выполнена. Эта возможность обеспечивается использованием условной переменной( conditional variable) вместе со взаимным исключением. Взаимное исключение используется для реализации блокирования, а условная переменная обеспечивает сигнальный механизм.
В терминах Pthreads условная переменная — это переменная типа
pthread_cond_t
. Такие переменные используются в следующих двух функциях:
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t * cptr, pthread_mutex_t * mptr);
int pthread_cond_signal(pthread_cond_t * cptr);
Обе
функции возвращают: 0 в случае успешного выполнения, положительное значение Exxx в случае ошибки
Слово
signal
в названии второй функции не имеет отношения к сигналам Unix
SIGxxx
.
Проще всего объяснить действие этих функций на примере. Вернемся к нашему примеру веб-клиента. Счетчик
ndone
теперь ассоциируется и с условной переменной, и с взаимным исключением:
Поток оповещает главный цикл о своем завершении, увеличивая значение счетчика, пока взаимное исключение принадлежит данному потоку (блокировано им), и используя условную переменную для сигнализации.
Pthread_mutex_lock(&ndone_mutex);
ndone++;
Pthread_cond_signal(&ndone_cond);
Pthread_mutex_unlock(&ndone_mutex);
Затем основной цикл блокируется в вызове функции
pthread_cond_wait
, ожидая оповещения о завершении выполнения потока:
while (nlefttoread > 0) {
while (nconn < maxnconn && nlefttoconn > 0) {
/* находим файл для чтения */
...
}
/* Ждем завершения выполнения какого-либо потока */
Pthread_mutex_lock(&ndone_mutex);
while (ndone == 0)
Pthread_cond_wait(&ndone_cond, &ndone_mutex);
for (i = 0; i < nfiles; i++) {
if (file[i].f_flags & F_DONE) {
Pthread_join(file[i].f_tid, (void**)&fptr);
/* обновляем file[i] для завершенного потока */
...
}
}
Pthread_mutex_unlock(&ndone_mutex);
}
Обратите внимание на то, что переменная
ndone
по-прежнему проверяется, только если потоку принадлежит взаимное исключение. Тогда, если не требуется выполнять какое-либо действие, вызывается функция
pthread_cond_wait
. Таким образом, вызывающий поток переходит в состояние ожидания, иразблокируется взаимное исключение, которое принадлежало этому потоку. Кроме того, когда управление возвращается потоку функцией
pthread_cond_wait
(после того как поступил сигнал от какого-либо другого потока), он снова блокирует взаимное исключение.