Unix pthreads и сигналы: для обработчиков сигналов потока

У меня возникли проблемы с получением потоков, чтобы поймать правильные сигналы.

Например,

Сначала я начинаю основной поток (tid 1).

Затем он устанавливает обработчик сигнала для SIGUSR1 в функцию1(), используя signal(2).

Основной поток создает новый поток, с tid 2.

В потоке 2 я регистрирую обработчик сигнала от SIGUSR1 до function2() с помощью signal(2).

Затем поток 1 создает поток 3 (tid 3).

Из потока 3 я использую pthread_kill(1, SIGUSR1) для отправки сигнала в поток 1.

Однако function2() вызывается, а не function1().

Предполагается ли это поведение, или есть что-то, что мне нужно изменить, чтобы заставить эти обработчики сигналов работать?

Изменить: я немного отлаживал, и получается, что сигнал IS отправляется в поток 1, однако function2() по какой-то причине вызывается из потока 1. Есть ли обходной путь для этого?

Ответ 1

В дополнение к alk answer:

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

Примечание. Сигналы доставляются в поток any, который явно не блокирует его доставку. Это не меняет этого. Вам все равно нужно использовать pthread_kill() или аналогичные механизмы для направления сигнала на конкретный поток; сигналы, которые поднимаются или отправляются процессу (вместо конкретного потока), по-прежнему будут обрабатываться случайным потоком (среди тех, которые его не блокируют).

Я не могу придумать какой-либо вариант использования, когда я лично предпочел бы такой подход; до сих пор всегда был какой-то другой способ, что-то еще легче и лучше. Итак, если вы планируете внедрить что-то подобное для реального приложения, пожалуйста, отстранитесь и пересмотрите свою логику приложений.

Но, поскольку метод возможен, вот как я могу его реализовать:

#include <signal.h>

/* Per-thread signal handler function pointer.
 * Always use set_thread_SIG_handler() to change this.
*/
static __thread void (*thread_SIG_handler)(int, siginfo_t *, void *) = (void *)0;

/* Process-wide signal handler.
*/
static void process_SIG_handler(int signum, siginfo_t *info, void *context)
{
    void (*func)(int, siginfo_t *, void *);

#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)
    func = __atomic_load_n(&thread_SIG_handler, __ATOMIC_SEQ_CST);
#else
    func = __sync_fetch_and_add(&thread_SIG_handler, (void *)0);
#endif

    if (func)
        func(signum, info, context);
}

/* Helper function to set new per-thread signal handler
*/
static void set_thread_SIG_handler(void (*func)(int, siginfo_t *, void *))
{
#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)
    __atomic_store_n(&thread_SIG_handler, func, __ATOMIC_SEQ_CST);
#else
    void (*oldfunc)(int, siginfo_t *, void *);
    do {
        oldfunc = thread_SIG_handler;
    } while (!__sync_bool_compare_and_swap(&thread_SIG_handler, oldfunc, func));
#endif
}

/* Install the process-wide signal handler.
*/
int install_SIG_handlers(const int signum)
{
    struct sigaction act;
    sigemptyset(&act.sa_mask);
    act.sa_sigaction = process_SIG_handler;
    act.sa_flags = SA_SIGACTION;
    if (sigaction(signum, &act, NULL))
        return errno;
    return 0;
}

Мне нравится выше, потому что он не требует pthreads, и он очень надежный и надежный. Помимо визуального беспорядка из-за того, что логика препроцессора выбирает, какой стиль атомных встроенных модулей используется, это очень просто, если вы внимательно посмотрите на него.

GCC 4.7 и более поздние версии предоставляют С++ 11-подобную __ атомные встроенные функции, более старые версии GCC и другие компиляторы (ICC, Pathscale, Portland Group) предоставляют __ sync legacy встроенные функции. __thread ключевое слово для локального хранилища потоков также должно быть доступно во всех существующих системах POSIX-y.

Если у вас есть архаичная система или настаивайте на соблюдении стандартов, следующий код должен иметь примерно эквивалентное поведение:

#include <pthread.h>
#include <signal.h>
#include <errno.h>

static pthread_key_t  thread_SIG_handler_key;

static void process_SIG_handler(int signum, siginfo_t *info, void *context)
{
    void (*func)(int, siginfo_t *, void *);

    *((void **)&func) = pthread_getspecific(thread_SIG_handler_key);
    if (func)
        func(signum, info, context);
}

static int set_thread_SIG_handler(void (*func)(int, siginfo_t *, void *))
{
    sigset_t block, old;
    int result;

    sigemptyset(&block);
    sigaddset(&block, SIG); /* Use signal number instead of SIG! */
    result = pthread_sigmask(SIG_BLOCK, &block, &old);
    if (result)
        return errno = result;

    result = pthread_setspecific(thread_SIG_handler_key, (void *)func);
    if (result) {
        pthread_sigmask(SIG_SETMASK, &old, NULL);
        return errno = result;
    }

    result = pthread_sigmask(SIG_SETMASK, &old, NULL);
    if (result)
        return errno = result;

    return 0;
}

int install_SIG_handlers(const int signum)
{
    struct sigaction act;
    int result;

    result = pthread_key_create(&thread_SIG_handler_key, NULL);
    if (result)
        return errno = result;

    sigemptyset(&act.sa_mask);
    act.sa_sigaction = process_SIG_handler;
    act.sa_flags = SA_SIGACTION;
    if (sigaction(signum, &act, NULL))
        return errno;

    return 0;
}

Я думаю, что самый близкий реальный эквивалент кода, подобный этому, который я когда-либо использовал, - это тот, где я использовал один сигнал в реальном времени (SIGRTMIN+0), заблокированный во всех, кроме одного потока, в качестве отражателя: он отправил другой сигнал реального времени (SIGRTMIN+1) для ряда рабочих потоков, чтобы прервать блокировку ввода-вывода. (Это можно сделать с помощью одного сигнала в реальном времени, но модель с двумя сигналами проще реализовать и упростить.)

Такое отражение сигнала или разветвление иногда полезно, и это не так отличается от этого подхода. Разные, чтобы оправдать свой собственный вопрос, если кто-то заинтересован.

Ответ 2

Невозможно установить обработчики сигналов "per-thread".

Из man 7 signal (выделение мной):

Распознавание сигнала - это атрибут для процесса: в многопоточном приложении расположение конкретного сигнала одинаково для всех потоков.

Тем не менее есть, чтобы направить каждый тип сигнала на другой поток, замаскировав прием любого количества типов сигналов на основе "за потоки".

О том, как направить набор сигнальных типов на конкретный поток, вы можете посмотреть этот ответ: fooobar.com/info/494845/...