# k6 -p214005 -s41 -n100
направляет процессу с PID = 214005 последовательность из 100 сигналов с кодом 41 (
SIGRTMIN
SIG = 41; TID = 4
Здесь удобный случай показать разницу между обработкой сигналов на базе очереди и простой обработкой (модель надежных сигналов). Для этого заменим две строки заполнения структуры
sigaction
act.sa_handler = handler;
act.sa_flags = 0;
а заголовок функции
handler()
static void handler(int signo)
Сколько бы раз мы ни повторяли тестирование, идентификатор потока, получающего и обрабатывающего сигнал, всегда будет равен 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);
}
}
Поведение приложения радикально изменится — происходит смена обрабатывающего потока (чтобы сократить объем вывода, серии посылаемых сигналов состоят из одного сигнала). Следует отметить, что смена обрабатывающего потока происходит между сериями, но ни в коем случае не внутри длинных серий, что можно проследить экспериментально.
SIG = 41, TID = 1
SIG = 41; TID = 4
SIG = 41; TID = 4
SIG = 41; TID = 1
SIG = 41; TID = 1
SIG = 41; TID = 4
SIG = 41; TID = 4
SIG = 41; TID = 1
SIG = 41; TID = 2
SIG = 41; TID = 2
SIG = 41; TID = 3
SIG = 41; TID = 4
Такая модель вряд ли может быть названа в полной мере «сигналами в потоках», так как сигнал в ней в конечном итоге направляется процессу как контейнеру, содержащему потоки (можно сказать и так: в оболочку адресного пространства процесса). И только после этого в контексте одного из потоков(и в случае множественных потоков, разблокированных на обработку единого сигнала, невозможно предсказать, в контексте какого из них) выполняется обработчик сигнала. Главный поток процесса (TID = 1) в этой схеме участвует в равнозначном качестве (здесь хорошо видно, что устоявшееся понятие «реакция процесса на сигнал» в строгом смысле некорректно).
Перейдем к более конкретным вопросам: как можно продуктивно использовать эту схему в многопоточных приложениях? Рассмотрим сначала случай, когда каждый из рабочих потоков разблокирован на получение одного, свойственного только ему сигнала ( файл s9.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;
}
void* threadfunc(void* data) {
// блокировать реакцию на все сигналы
sigset_t sig;
sigfillset(&sig);
SignalProcmask(0, 0, SIG_BLOCK, &sig, NULL);
// разблокировать реакцию на свой сигнал
sigemptyset(&sig);
sigaddset(&sig, (int)data);
SignalProcmask(0, 0, SIG_UNBLOCK, &sig, NULL);
// цикл ожидания приходящих сигналов
while (true) pause();
}
int main() {