Тайм-ауты ядра и функция pthread_join()
Тайм-ауты ядра и функция pthread_join()
Самый простой пример для рассмотрения — это использование тайм-аута с функцией pthread_join(). Вот как это можно было бы сделать:
/*
* tt1.c
*/
#include <stdio.h>
#include <pthread.h>
#include <inttypes.h>
#include <errno.h>
#include <sys/neutrino.h>
#define SEC_NSEC 1000000000LL // В одной секунде
// 1 биллион наносекунд
void* long_thread(void *notused) {
printf("Этот поток выполняется более 10 секунд ");
sleep(20);
}
int main(void) // Игнорировать аргументы
{
uint64_t timeout;
struct sigevent event;
int rval;
pthread_t thread_id;
// Настроить событие — это достаточно сделать однажды
// Либо так, либо event.sigev_notify = SIGEV_UNBLOCK:
SIGEV_UNBLOCK_INIT(&event);
// Создать поток
pthread_create(&thread_id, NULL, long_thread, NULL);
// Установить тайм-аут 10 секунд
timeout = 10LL * SEC_NSEC;
TimerTimeout(CLOCK_REALTIME, _NTO_TIMEOUT_JOIN, &event,
&timeout, NULL);
rval = pthread_join(thread_id, NULL);
if (rval == ETIMEDOUT) {
printf("Истекли 10 секунд, поток %d все еще"
" выполняется! ",
thread_id);
}
sleep(5);
TimerTimeout(СLOCK_REALTIME, _NTO_TIMEOUT_JOIN, &event,
&timeout, NULL);
rval = pthread_join(thread_id, NULL);
if (rval == ETIMEDOUT) {
printf("Истекли 25 секунд, поток %d все еще выполняется"
" (нехорошо)! ",
thread_id);
} else {
printf("Поток %d завершен (как и ожидалось!) ",
thread_id);
}
}
Мы применили макроопределение SIGEV_UNBLOCK_INIT() для инициализации структуры события, но можно было установить sigev_notify в SIGEV_UNBLOCK и «вручную». Можно было даже сделать еще более изящно, передав NULL вместо struct sigevent — функция TimerTimeout() понимает это как знак, что нужно использовать SIGEV_UNBLOCK.
Если поток (заданный в thread_id) остается работающим более 10 секунд, то системный вызов завершится по тайм-ауту — функция pthread_join() возвратится с ошибкой, установив errno в ETIMEDOUT.
Вы можете использовать и другую «стенографию», указав NULL в качестве значения тайм-аута (параметр ntime в декларации выше), что предпишет ядру не блокироваться в данном состоянии. Этот прием можно использовать для организации программного опроса. (Хоть программный опрос и считается дурным тоном, его можно весьма эффективно использовать в случае с pthread_join(), периодически проверяя, завершился ли нужный поток. Если нет, можно пока сделать что-нибудь другое.)
Ниже представлен пример программы, в которой демонстрируется неблокирующий вызов pthread_join():
int pthread_join_nb(int tid, void **rval) {
TimerTimeout(CLOCK_REALTIME, _NTO_TIMEOUT_JOIN,
NULL, NULL, NULL);
return (pthread_join(tid, rval));
}
Тайм-ауты ядра при обмене сообщениями
Все становятся несколько сложнее, когда вы используете тайм-ауты ядра при обмене сообщениями. Вспомните главу «Обмен сообщениями», раздел «Обмен сообщениями и модель «клиент/сервер») — на момент отправки клиентом сообщения сервер может как ожидать его, так и нет. Это означает, что клиент может заблокироваться как по передаче (если сервер еще не принял сообщение), так и по ответу (если сервер принял сообщение, но еще не ответил). Основной смысл здесь в том, что вы должны предусмотреть оба блокирующих состояния в параметре flags функции TimerTimeout(), потому что клиент может оказаться в любом из них.
Чтобы задать несколько состояний, сложите их операцией ИЛИ (OR):
TimerTimeout(... _NTO_TIMEOUT_SEND | _NTO_TIMEOUT_REPLY,
...);
Это вызовет тайм-аут всякий раз, когда ядро переведет клиента в состояние блокировки по передаче (SEND) или по ответу (REPLY). В тайм-ауте SEND-блокировки нет ничего особенного — сервер еще не принял сообщение, значит, ничего для этого клиента он не делает. Это значит, что если ядро генерирует тайм-аут для SEND-блокированного клиента, сервер об этом информировать не обязательно. Функция MsgSend() клиента возвратит признак ETIMEDOUT и обработка тайм-аута завершится.
Однако, как было упомянуто в главе «Обмен сообщениями» (параграф «_NTO_CHF_UNBLOCK»), если сервер уже принял сообщение клиента, и клиент желает разблокироваться, для сервера существует два варианта реакции. Если сервер не указал флаг _NTO_CHF_UNBLOCK на канале, по которому было принято сообщение, клиент будет разблокирован немедленно, и сервер не получит об этом никакого оповещения. У большинства серверов, которые мне доводилось встречать, флаг _NTO_CHF_UNBLOCK был всегда установлен. В этом случае ядро посылает серверу импульс, а клиент остается заблокированным до тех пор, пока сервер ему не ответит! Как было показано в вышеупомянутом разделе главы «Обмен сообщениями», это сделано для того, чтобы сервер мог узнать о запросе клиента на разблокирование и выполнить по этому поводу какие-то действия.