The Developer’s Cry

Yet another blog by a hobbyist programmer

pthreads and UNIX signals

A while ago, I wrote in this blog entry that when sending signals to a multi-threaded process, it is unclear which thread receives the signal. This little problem is actually easy to solve when you realize that you can create a seperate thread whose sole purpose it is to catch signals and respond to them.

You can mask out certain signals using sigprocmask() and its threaded nephew, pthread_sigmask(). These signals will be effectively disabled and will never be received in these contexts.
In the “signal processing” thread, enable the blocked signals. This will now be the only thread who receives the signals.

In almost finished C-code:

int main(int argc, char *argv[]) {
sigset_t set;

    /* create signal processing thread */
    pthread_create(&thread_id, NULL, signal_processor, NULL);

    /*
        in the main thread, set up the desired signal mask, common to most threads
        any newly created threads will inherit this signal mask
    */
    sigemptyset(&set);
    sigaddset(&set, SIGHUP);
    sigaddset(&set, SIGINT);
    sigaddset(&set, SIGUSR1);
    sigaddset(&set, SIGUSR2);
    sigaddset(&set, SIGALRM);
/*  sigaddset(&set, SIGWHATEVER); */

    /* block out these signals */
    sigprocmask(SIG_BLOCK, &set, NULL);

    /* create other threads */
    pthread_create(...);

    /*  ... */

/*  pthread_join(...); */

    /*  ... */

    return 0;
}

/*
    implementation of the signal processing thread
*/
void *signal_processor(void *arg) {
    /*
        install signal handlers for interesting signals
        I choose to install dummy handlers (see below for why)
    */
    install_signal_handler(SIGHUP);
    install_signal_handler(SIGINT);
    install_signal_handler(SIGUSR1);
    install_signal_handler(SIGUSR2);
    install_signal_handler(SIGALRM);
/*  install_signal_handler(SIGWHATEVER); */

    for(;;) {      /* forever */
        /*
            wait for any signal
        */
        sigfillset(&set);
        sigwait(&set, &sig);

        /*
            handle the signal here, rather than in a signal handler
        */
        switch(sig) {
            case SIGTERM:
                exit(1);

            case SIGHUP:
                reload_config();
                break;

            default:
                printf("caught signal %d\n", sig);
        }
    }
}

void dummy_signal_handler(int sig) {
    /* nothing */
    ;
}

So, the code first creates a signal processing thread. This thread has the default signal mask and will therefore receive signals. The main thread then installs a signal mask that blocks most signals. Any threads created from the main thread will inherit this new signal mask.
The signal processing thread uses sigaction() (not shown here) to install signal handlers. These signal handlers are empty dummies. The reason for this is, by installing a signal handler you tell the operating system that you do not want the default signal action for these signals. Next, call sigwait(), so wait indefinately until any signal arrives. Then, handle the caught signal.
It is possible to install useful signal handlers instead of using the dummies, but I’d much rather use sigwait() and make signal handling synchronous as opposed to asynchronous. (The threads are still run asynchonously in respect to each other, though).

It is tempting to simply use sigfillset() and block all signals. While this is perfectly possible, it does have some implications. Any errors that generate signals like SIGSEGV, SIGBUS, SIGILL, will be blocked in the thread that generated the error, and caught in the signal processing thread. Be wary of this, as this is likely to produce debugging headaches.

There is a pthread_kill() function that you can use to send signals to threads. Do not use this mechanism to communicate between threads. Threads should synchronize and communicate using mutexes, barriers, and condition variables, and if you want to terminate a thread you should use pthread_cancel(), or communicate to it that it should exit, so that it can call pthread_exit() by itself.
The only use of pthread_kill() is to copy the signal to other threads that should also receive it, if you have a (weird) code where multiple threads can be signalled.