UNIX: разработка сетевых приложений
}Pthread_mutex_unlock(&ndone_mutex);}Это означает, что главный поток никогда не переходит в спящее состояние, а просто входит в цикл, проверяя каждый раз значение переменной
. Этот процесс называется опросом (polling) и рассматривается как пустая трата времени центрального процессора.ndoneНам нужен метод, с помощью которого главный цикл мог бы входить в состояние ожидания, пока один из потоков не оповестит его о том, что какая-либо задача выполнена. Эта возможность обеспечивается использованием условной переменной (conditional variable) вместе со взаимным исключением. Взаимное исключение используется для реализации блокирования, а условная переменная обеспечивает сигнальный механизм.
В терминах Pthreads условная переменная — это переменная типа
. Такие переменные используются в следующих двух функциях:pthread_cond_t#include <pthread.h>int pthread_cond_wait(pthread_cond_t *<i>cptr</i>, pthread_mutex_t *<i>mptr</i>);int pthread_cond_signal(pthread_cond_t *<i>cptr</i>);<i>Обе функции возвращают: 0 в случае успешного выполнения, положительное значение Exxx в случае ошибки</i>Слово
в названии второй функции не имеет отношения к сигналам Unixsignal.SIGxxxПроще всего объяснить действие этих функций на примере. Вернемся к нашему примеру веб-клиента. Счетчик
теперь ассоциируется и с условной переменной, и с взаимным исключением:ndoneint ndone;pthread_mutex_t ndone_mutex = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t ndone_cond = PTHREAD_COND_INITIALIZER;Поток оповещает главный цикл о своем завершении, увеличивая значение счетчика, пока взаимное исключение принадлежит данному потоку (блокировано им), и используя условную переменную для сигнализации.
Pthread_mutex_lock(&ndone_mutex);ndone++;Pthread_cond_signal(&ndone_cond);Pthread_mutex_unlock(&ndone_mutex);Затем основной цикл блокируется в вызове функции
, ожидая оповещения о завершении выполнения потока:pthread_cond_waitwhile (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Почему взаимное исключение всегда связано с условной переменной? «Условие» обычно представляет собой значение некоторой переменной, используемой совместно несколькими потоками. Взаимное исключение требуется для того, чтобы различные потоки могли задавать и проверять значение условной переменной. Например, если в примере кода, приведенном ранее, отсутствовало бы взаимное исключение, то проверка в главном цикле выглядела бы следующим образом:
/* Ждем завершения выполнения одного или нескольких потоков */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