case Y :
enqueue request to YQueue
signal Thread[2]
case Z :
enqueue request to ZQueue
signal Thread[3]
//...
}
end loop
}
void *taskX(void *X)
{
loop
suspend until awaken by boss
loop while XQueue is not empty
dequeue request
process request
end loop
until done
{
void *taskY(void *Y)
{
loop
suspend until awaken by boss
loop while YQueue is not empty
dequeue request
process request
end loop
until done
}
void *taskZ(void *Z)
{
loop
suspend until awaken by boss
loop while (ZQueue is not empty)
dequeue request
process request
end loop
until done
} //.. .
В листинге 4.7 управляющий поток создает N рабочих потоков (по одному для каждого типа задачи). Каждая задача связана с обработкой запросов некоторого типа В цикле событий управляющий поток извлекает запрос из очереди запросов, определяет его тип, ставит его в очередь запросов, соответствующую типу, а затем оправляет сигнал потоку, который обрабатывает запросы из этой очереди. Функции потоков также содержат циклы событий. Поток приостанавливается до тех пор, пока не получит сигнал от управляющего потока о существовании запроса в его очереди. После «пробуждения» (уже во внутреннем цикле) поток обрабатывает все запросы до тех пор, пока его очередь не опустеет.
Использование модели сети с равноправными узлами
В модели равноправных узлов один поток сначала создает все потоки, необходимые выполнения всех задач. Каждый из равноправных потоков обрабатывает запросы, поступающие из собственного входного потока данных. В листинге 4.8 представлен скелет программы, реализующий при разделении программы на потоки метод равноправных узлов
Листинг 4.8. Скелет программы реализации модели равноправных потоков
pthread_t Thread[N]
// initial thread
{
pthread_create(&(Thread[1]...taskX...);
pthread_create(&(Thread[2]...taskY...);
pthread_create(&(Thread[3]...taskZ...);
//...
}
void *taskX(void *X)
{
loop while (Type XRequests are available)
extract Request
process request
end loop
return(NULL)
}
В модели равноправных потоков каждый поток отвечает за собственный входной поток данных. Входные данные могут быть выделены из базы данных, файла и т.п.
Использование модели конвейера
В модели конве йера поток входных данных обрабатывается поэтапно. На каждом этапе некоторая порция работы (часть входного потока данных) обрабатывается одним потоком выполнения, а затем передается для обработки следующему. Каждая порция входных данных переходит на очередной этап обработки до тех пор, пока не будет завершена вся обработка. Такой подход позволяет обрабатывать несколько входных потоков данных одновременно. Каждый поток выполнения отвечает за достижение пром ежуточного результата, делая его доступным для следующего этапа (т.е. следующего потока конвейера). Скелет программы реализации модели конвейера представлен в листинге 4.9.
// Листинг 4.9. Скелет программы реализации модели конвейера
//...
pthread_t Thread[N]
Queues[N]
// initial thread
{
place all input into stage1's queue
pthread_create(&(Thread[1]...stage1...);
pthread_create(&(Thread[2]...stage2...);
pthread_create(&(Thread[3]...stage3...);
//...
}
void *stageX(void *X)
{
loop
suspend until input unit is in queue
loop while XQueue is not empty
dequeue input unit
process input unit
enqueue input unit into next stage's queue
end loop
until done
return(NULL)
}
В листинге 4.9 объявляется N очередей для N этапов. Начальный поток помещает все порции входных потоков в очередь первого этапа, а затем создает все потоки, необходимые для выполнения всех этапов. Каждый этап содержит свой цикл событий. Поток выполнения находится в состоянии ожидания до тех пор, пока в его очереди не появится порция входных данных. Внутренний цикл продолжается до опустения соответствующей очереди. Порция входных данных извлекается из очереди, обрабатывается, а затем помещается в очередь следующего этапа обработки (следующего потока выполнения).
Использование модели «изготовитель-потребитель»
В модели «изготовитель-потребитель» поток- «изготовитель» готовит данные, «потребляемые» потоком-«потребителем» (причем таких потоков-«потребителей" может быть несколько). Данные хранятся в блоке памяти, разделяемом всеми потока, как изготовителем, так и потребителями. В листинге 4.10 представлен скелет программы реализации модели «изготовитель-потребитель» (эта модель также использовалась в программах 4.5, 4.6 и 4.7).
Листинг 4.10. Скелет программы реализации модели «изготовитель-потребитель»
pthread_mutex_t Mutex = PTHREAD_MUTEX_INITIALIZER
pthread_t Thread[2]
Queue
// initial thread
{
pthread_create(&(Thread[1]...producer...);
pthread_create(&(Thread[2]...consumer...);
//...
}
void *producer(void *X)
{
loop
perform work
pthread_mutex_lock(&Mutex)
enqueue data
pthread_mutex_unlock(&Mutex)
signal consumer
//...
until done
}
void *consumer(void *X)
{
loop
suspend until signaled
loop while(Data Queue not empty)
pthread_mutex_lock(&Mutex)
dequeue data
pthread_mutex_unlock(&Mutex)
perform work
end loop
until done
}
//
В листинге 4.9 начальный поток создает оба потока: «изготовителя» и «потребителя». Поток- «изготовитель» содержит цикл, в котором после выполнения некоторых действий блокируется мьютекс для совместно используемой очереди, чтобы поместить в нее подготовленные для потребителя данные. После этого «изготовитель» деблокирует мьютекс и посылает сигнал потоку- «потребителю» о том, что ожидаемые им данные уже находятся в очереди. Поток- «изготовитель» выполняет итерации цикла до тех пор, пока не будет выполне н а вся работа. Поток- «потребитель» также выполняет цикл, в котором он приостанавливается до тех пор, пока не получит сигнал. Во внутреннем цикле поток- «потребитель» обрабатывает все данные до тех пор, пока не опустеет очередь. Он блокирует мьютекс для разде л яемой очереди перед извлечением из нее данных и деблокирует мьютекс после этого. Затем он выполняет обработку извлеченных данных. В программе 4.6 поток-«потребитель» помещает свои результаты в файл. Вместо файла может быть использована другая структура данных. Зачастую потоки-«потребители» играют две роли: как потребителя, так и изготовителя. Сначала возможно «исполнение» роли потребителя необработанных данных, подготовленных потоком-«изготовителем», а затем поток играет роль «изготовителя», когда он обрабатывает данные, сохраняемые в другой совместно используемой очереди, «потребляемой» другим потоком.