Чтение онлайн

ЖАНРЫ

QNX/UNIX: Анатомия параллелизма
Шрифт:

sigset_t* oldset);

Видна прямая аналогия с рассматривавшейся ранее функцией

sigprocmask
. Да это и неудивительно, поскольку
sigprocmask
является POSIX-«оберткой» к
SignalProcmask
. Только рассматриваемый вызов имеет два «лишних» начальных параметра: PID и TID потока, к маске которого применяется действие. Если
pid
— 0, то предполагается текущий процесс, если tid = 0, то
pid
игнорируется и предполагается текущий поток, вызывающий функцию.

Остальные параметры соответствуют параметрам

sigprocmask
(дополнительно
появляется возможное значение
SIG_PENDING
для
how
).

Рассмотрим, как это работает на примере простейшего кода ( файл s6.cc):

Сигналы, обрабатываемые в потоках

#include <stdio.h>

#include <iostream.h>

#include <signal.h>

#include <unistd.h>

#include <pthread.h>

#include <time.h>

#include <sys/neutrino.h>

static void handler(int signo, siginfo_t* info, void* context) {

cout << "SIG = " << signo << ";

TID = " << pthread_self << endl;

}

sigset_t sig;

void* threadfunc(void* data) {

SignalProcmask(0, 0, SIG_UNBLOCK, &sig, NULL);

while (true) pause;

}

int main {

sigemptyset(&sig);

sigaddset(&sig, SIGRTMIN);

sigprocmask(SIG_BLOCK, &sig, NULL);

cout << "Process " << getpid << ", waiting for signal " <<

SIGRTMIN << endl;

struct sigaction act;

act.sa_mask = sig;

act.sa_sigaction = handler;

act.sa_flags = SA_SIGINFO;

if (sigaction(SIGRTMIN, &act, NULL) < 0)

perror("set signal handler: ");

const int thrnum = 3;

for (int i = 0; i < thrnum; i++)

pthread_create(NULL, NULL, threadfunc, NULL);

pause;

exit(EXIT_SUCCESS);

}

Для анализа этого и последующих фрагментов нам будет недостаточно команды

kill
, поэтому сделаем простейший «передатчик» плотной (в смысле минимального интервала следования) последовательности повторяющихся сигналов ( файл k6.cc). Выполнение этого тестера, например по команде:

# k6 -p214005 -s41 -n100

направляет процессу с PID = 214005 последовательность из 100 сигналов с кодом 41 (

SIGRTMIN
). Посылая нашему процессу-тестеру последовательность из N сигналов,
мы получим N сообщений вида:

SIG = 41; TID = 4

Примечание

Здесь удобный случай показать разницу между обработкой сигналов на базе очереди и простой обработкой (модель надежных сигналов). Для этого заменим две строки заполнения структуры

sigaction
на:

act.sa_handler = handler;

act.sa_flags = 0;

а заголовок функции

handler
перепишем так:
static void handler(int signo)
. Если теперь мы в точности повторим предыдущий тест, то при посылке процессу- тестеру последовательности из N сигналов мы получим всего одно сообщение все того же вида. Это наблюдение интересно еще и тем, что оно показывает, что алгоритм взаимодействия сигнала с потоками не зависит от того, какая обработка установлена для этого сигнала: на основе модели сигналов реального времени или на основе модели надежных сигналов.

Сколько бы раз мы ни повторяли тестирование, идентификатор потока, получающего и обрабатывающего сигнал, всегда будет равен 4.Что же происходит:

• главный поток (TID = 1) создает 3 новых потока (TID = 2, 3, 4);

• главный поток переходит в пассивное ожидание сигналов, но в его маске доставка посылаемого сигнала (41) заблокирована;

• выполнение функции потока начинается с разблокирования ожидаемого сигнала;

• … 3 потока (TID = 2, 3, 4) ожидают поступления сигнала;

• при поступлении серии сигналов вся их очередь доставляется и обрабатывается одним потоком с TID = 4, который тут же в цикле возвращается к ожиданию следующих сигналов.

Таким образом, сигнал доставляется одному и только одному потоку, который не блокирует этот сигнал. Обработчик сигнала вызывается в контексте (стек, области собственных данных) этого потока. После выполнения обработчика сигнал поглощается. Какому из потоков, находящихся в состоянии блокирования в ожидании сигналов (в масках которых разблокирован данный сигнал), будет доставлен экземпляр сигнала, предсказать невозможно; это так и должно быть исходя из общих принципов диспетчеризации потоков. Но реально этим потоком является поток, последнимперешедший в состояние ожидания. Для того чтобы убедиться в этом, заменим предпоследнюю строку программы (

pause;
) на:

threadfunc(NULL);

Теперь у нас 4 равнозначных потока, ожидающих прихода сигнала, переходящих в состояние ожидания в последовательности: TID = 2, 3, 4, 1. Реакция процесса на приход сигнала изменится на:

SIG = 41, TID = 1

Изменим текст функции потока на ( файл s7.cc):

void* threadfunc(void* data) {

while (true) {

SignalProcmask(0, 0, SIG_UNBLOCK, &sig, NULL);

delay(1);

SignalProcmask(0, 0, SIG_BLOCK, &sig, NULL);

delay(10);

}

}

Поведение приложения радикально изменится — происходит смена обрабатывающего потока (чтобы сократить объем вывода, серии посылаемых сигналов состоят из одного сигнала). Следует отметить, что смена обрабатывающего потока происходит между сериями, но ни в коем случае не внутри длинных серий, что можно проследить экспериментально.

Поделиться с друзьями: