33 }
Мы объявляем взаимное исключение с именем
counter_mutex
. Это исключение должно быть заблокировано потоком на то время, когда он манипулирует переменной counter. Когда мы запускали эту программу, результат всегда был правильным: значение переменной увеличивалось монотонно, а ее окончательное значение всегда оказывалось равным 10 000.Насколько серьезной является дополнительная нагрузка, связанная с использованием взаимных исключений? Мы изменили программы, приведенные в листингах 26.11 и 26.12, заменив значение
NLOOP
на 50 000 (вместо исходного значения 5000), и засекли время, направив вывод на устройство
/dev/null
. Время работы центрального процессора в случае корректной версии, использующей взаимное исключение, увеличилось относительно времени работы некорректной версии без взаимного исключения на 10 %. Это означает, что использование взаимного исключения не связано со значительными издержками.26.8. Условные переменные
Взаимное исключение позволяет предотвратить одновременный доступ к совместно используемой (разделяемой) переменной, но для того чтобы перевести поток в состояние ожидания (спящее состояние) до момента выполнения некоторого условия, необходим другой механизм. Продемонстрируем сказанное на следующем примере. Вернемся к нашему веб-клиенту из раздела 26.6 и заменим функцию Solaris
thr_join
на
pthread_join
. Но мы не можем вызвать функцию
pthread_join
до тех пор, пока не будем знать, что выполнение потока завершилось. Сначала мы объявляем глобальную переменную, которая служит счетчиком количества завершившихся потоков, и организуем управление доступом к ней с помощью взаимного исключения.int ndone; /* количество потоков, завершивших выполнение */
pthread_mutex_t ndone_mutex = PTHREAD_MUTEX_INITIALIZER;
Затем мы требуем, чтобы каждый поток по завершении своего выполнения увеличивал этот счетчик на единицу, используя соответствующее взаимное исключение.
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
. Этот процесс называется
Нам нужен метод, с помощью которого главный цикл мог бы входить в состояние ожидания, пока один из потоков не оповестит его о том, что какая-либо задача выполнена. Эта возможность обеспечивается использованием
В терминах Pthreads условная переменная — это переменная типа
pthread_cond_t
. Такие переменные используются в следующих двух функциях:#include pthread.h
int pthread_cond_wait(pthread_cond_t *
int pthread_cond_signal(pthread_cond_t *
Слово
signal
в названии второй функции не имеет отношения к сигналам Unix
SIGxxx
.Проще всего объяснить действие этих функций на примере. Вернемся к нашему примеру веб-клиента. Счетчик
ndone
теперь ассоциируется и с условной переменной, и с взаимным исключением:int ndone;