1
votes

I'm experiencing signals' reaction, being used with threads. I know printf family functions are not signal-safe functions but for the simplicity I use. The following is said that

When a signal is delivered to a multithreaded process that has established a signal handler, the kernel arbitrarily selects one thread in the process to which to deliver the signal and invokes the handler in that thread. This behavior is cosistent with maintaining the traditional signal semantics. It would not make sense for a process to perform the signal handling actions multiple times in response to a single signal.

To experience the bolded text, I write the following basic code, but when I press Ctrl-C, I get a really different thread id rather than the created ones. Who is this? Why cannot I see that a different thread can catch the signal.

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

pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cv = PTHREAD_COND_INITIALIZER;

int count = 0;
int globerr;

void *inc(void *arg) {
    fprintf(stderr, "Thread id = %d\n", (int)pthread_self());
    sleep(2);
    int error;
    for (int i = 0; i < 15; ++i) {
        if (error = pthread_mutex_lock(&mtx)) {
            globerr = error;
            return 0;
        }

        count++;
        printf("thread id = %d, count = %d\n", pthread_self(), count);
        if (count == 20) {

            printf("thread id = %d, SIGNAL count = %d\n", pthread_self(), count);
            pthread_cond_signal(&cv);
        }


        if (error = pthread_mutex_unlock(&mtx)) {
            globerr = error;
            return 0;
        }
    }
}

void* watcher(void *arg) {
    fprintf(stderr, "Thread id = %d\n", (int)pthread_self());
    sleep(2);
    int error;
    if (error = pthread_mutex_lock(&mtx)) {
        globerr = error;
        return 0;
    }

    while (count < 20) {
        printf("watcher thread id = %d, BLOCKING count = %d\n", pthread_self(), count);
        pthread_cond_wait(&cv, &mtx);
        printf("watcher thread id = %d, UNBLOCKING count = %d\n", pthread_self(), count);

    }


    printf("watcher thread id = %d, count = %d\n", pthread_self(), count);

    if (error = pthread_mutex_unlock(&mtx)) {
        globerr = error;
        return 0;
    }

}

static void signal_handler(int sig){
    if (sig == SIGINT)
        printf("Caught signal for Ctrl+C, Thread id = %d\n", (int)pthread_self());

    pthread_cancel(pthread_self());
}

int main(void) {
    struct sigaction sigact;
    sigact.sa_handler = signal_handler;
    sigemptyset(&sigact.sa_mask);
    sigact.sa_flags = 0;
    sigaction(SIGINT, &sigact, (struct sigaction *)NULL);

    pthread_t t[3];
    for (int i = 0; i < 2; ++i) {
        pthread_create(&t[i], 0, inc, 0);
    }

    pthread_create(&t[2], 0, watcher, 0);

    sleep(3);

    for (int i = 0; i < 3; ++i) {
        pthread_join(t[i], 0);
    }
}

Example output,

MacBook-Pro-2:cmake-build-debug soner$ ./client 
Thread id = 171921408
Thread id = 172457984
Thread id = 172994560
^CCaught signal for Ctrl+C, Thread id = 360719808
^CCaught signal for Ctrl+C, Thread id = 171921408
^CCaught signal for Ctrl+C, Thread id = 172457984
^CCaught signal for Ctrl+C, Thread id = 172994560

Who is 360719808?

1
You forget the main thread, the one that starts the other tree. - Some programmer dude
"I know printf family functions are not signal-safe functions but for the simplicity I use." -- then any answer we give is speculative. Your program has undefined behavior, which might or might not impact the observation you asked about. Do not knowingly invoke UB -- there is no upside. - John Bollinger
@Someprogrammerdude, I run the code several times, seemingly always the main thread catches the signal firstly. Why cannot I see one of the others while handling at first? - snr
@snr, "the kernel arbitrarily selects" does not imply that it selects randomly. The mechanism by which it chooses is unspecified and may vary, but one plausible possibility is that it scans the process's threads in some pre-defined order until it finds one to which it can deliver the signal (because it is not blocked by that thread). If the main thread has the signal blocked, but the others don't, then the kernel should select one of the others. - John Bollinger
"Arbitrary" doesn't mean "random". Perhaps the kernel just prefer to select the main thread if possible? For e.g. Linux you could check its implementation (with it being open-source and all), to see its selection processing. - Some programmer dude

1 Answers

1
votes

If you call pthread_create three times, your program has four threads: the fourth is the initial thread, the one that existed from the beginning of your program. You can think of it as the thread whose thread procedure is main.

In a program like this, you should dedicate a single thread to signal handling. You do that in four steps:

  1. In main, before creating any threads, use pthread_sigmask to block all signals except the ones that indicate synchronous fatal events (SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGSYS, and SIGTRAP). Do not install any signal handlers.
  2. Create a thread with a thread procedure dedicated to signal handling.
  3. That thread loops calling sigwaitinfo for the signals you care about. This must include at least the signals that indicate an external termination or suspension request: SIGINT, SIGHUP, SIGPWR, SIGQUIT, SIGTERM, SIGTSTP, SIGXCPU. If you use child processes you must also include SIGCHLD.
  4. Your other threads do whatever they need to do without worrying about signals. They probably spend most of their time either doing computations or blocked on select.