Yves
Yves

Reputation: 12371

signal handler and multithreading

I know that signal is a way of communication between CPU and OS kernel. A signal can interrupt the sequence of instructions, execute the handler and go back to the sequence of instructions.

Here is a piece of description of this link:

When a signal that is being caught is handled by a process, the normal sequence of instructions being executed by the process is temporarily interrupted by the signal handler. The process then continues executing, but the instructions in the signal handler are now executed. If the signal handler returns, the process continues executing the normal sequence of instructions it was executing when the signal was caught.

Here is my test:

void func(int p)
{
    cout<<"int"<<endl;
    sleep(5);
}

int main()
{
    signal(SIGINT, func);
    while(1)
    {
        cout<<"main"<<endl;
        sleep(2);
    }   
    return 0;
}

I execute it and if I press ctrl+C, int will be printed and then I have to wait for 5 seconds, no matter how many times ctrl+C I pressed while waiting for the 5 seconds, nothing happened.

Then I make another test:

void func(int p)
{
    cout<<"int"<<endl;
    sleep(5);
}

int main()
{
    signal(SIGINT, func);
    thread t1([](){while(1){cout<<"t1"<<endl; sleep(2);}});
    thread t2([](){while(1){cout<<"t2"<<endl; sleep(2);}});
    t1.join();
    t2.join();

    return 0;
}

For this code, I found that I could press ctrl+C three times continually and three int would be printed, then I have to wait for about 5 seconds.

So it seems that the first ctrl+C interrupts the t1 thread, the second interrupts the t2 thread and the third interrupts the main thread.

So signal only interrupt thread instead of the whole process if there are multi-threading?

Upvotes: 2

Views: 5176

Answers (3)

roschach
roschach

Reputation: 9336

INTRODUCTION

First of all each thread has its own mask that specifies which signals it is listening to. When a thread is created it inherits the mask of the thread that is creating it (let me call it the parent thread) that is active when pthread_create is called.

Generally it is better that if a thread is listening to a signal then the others should not, unless you want to have many threads that do the same stuff (for example when processing a connection request in a server to process many requests simultaneously). In this way you always know which thread function is processing the signal. Otherwise you do not have any idea which thread function is executing given the signal, making it impossible to debug (an example is your own question).

To change the mask of the child thread that is being created from the parent thread you create a new mask, you set it active, create a new thread with pthread_create and then in the parent thread set the previous mask active again (see the code at the end of the answer).

EDIT: According to this post it is better to use sigaction() instead of signal. In modern systems signal() is implemented with sigaction() so there should not be any difference. However if its old implementation is used, problems might arise.

ANSWER

So signal only interrupt thread instead of the whole process if there are multi-threading?

NO: Signals are just signals, they do not do anything. Actions associated to signals have the power to do stuff, including stopping the program or terminating the thread. Each signal has a default action associated and the default action for SIGINT is to interrupt the process.

With you handler you are overwriting the default action. So it will not stop the program anymore but it will do what you have specified in the thread function.

In the first case you have only one thread, the main one, which is an infinite loop, it always catches the signal as long as he is alive so that's why the behavior. If you resend the signal, the signal is momentarily blocked until the signal handler ends the execution. Nevertheless if many signals are sent while the handler is executing, you can loose some signals. In fact as explained here, blocked signals are set to pending, but not queued. The term pending means that the operating system remembers that there is a signal waiting to be delivered at the next opportunity by setting a flag, and not queued means that it does this by setting a flag somewhere, but not by keeping an exact record of how many signals have arrived. So if the signals is sent once, 5 times or more (try to press CTRL+C more times with your program: I have tried) while the signal handler is executing it produces the exactly same behaviour.

In your second case you have 3 threads: the main one, t1, and t2: all of them are enabled to see the signal SIGINT and all of them have associated the same signal handler. If you press 3 times one after the other all three of them will execute the handler: that is why you don't see any delay. If you press very very fast though and more than 3 times (the number of threads listening to that signal) I think you will see something similar to the first behaviour.

I will end my answer with the code I posted in a question where I was setting masks so that some signals were caught only by the main thread:

int main()
{
    int err;
    sigset_t omask, mask;
    pthread_t thread_motionSensor;
    pthread_t thread_tempReading;
    pthread_t thread_platformPost;

    printf("Created threads IDs\n");

    ...
    if (signal(SIGINT, sig_handler)==SIG_ERR)
        printf("Error on recording SIGINT HANDLER\n");

    /*Create a new mask to block all signals for the following thread*/
    sigfillset(&mask);
    pthread_sigmask(SIG_SETMASK, &mask, &omask);
    printf("Trying to create threads\n");
    if ((err = pthread_create (&thread_motionSensor, NULL, task1, NULL))!=0)
    {
    printf("Thread 1 not created: error %d\n", err);
        err_exit((const char)err, "pthread_create error");
    }
    printf("Thread 1 created. Trying to create Thread 2\n");
    if((err = pthread_create (&thread_tempReading,   NULL, task2, NULL))!=0)
    {
    printf("Thread 2 not created: error %d\n", err);
        err_exit((const char)err, "pthread_create error");
    }
    printf("Thread 2 created. Trying to create Thread 3\n");
    if ((err = pthread_create (&thread_platformPost, NULL, task3, NULL))!=0)
    {
     printf("Thread 3 not created: error %d %d\n", err);
         err_exit((const char)err, "pthread_create error");
    }
    printf("Thread 3 created\n");
    /*The main thread must block the SIGALRM but catch SIGINT
    SIGQUIT, SIGTERM, SIgkILL*/
    /*empty the omask set from all signals */
    sigemptyset(&omask);
    /*add the signals to the omask*/
    sigaddset(&omask, SIGINT);
    sigaddset(&omask, SIGQUIT);
    sigaddset(&omask, SIGKILL);
    sigaddset(&omask, SIGTERM);
    /*unblock all signals in omask*/
    pthread_sigmask(SIG_UNBLOCK, &omask, NULL);
    printf("Main thread waiting for signal\n");
    /*pause will stop the main thread until any signal not blocked by omask will be received*/
    pause();
    printf("Exit signal received: cancelling threads\n");

    pthread_cancel(thread_motionSensor);
    pthread_cancel(thread_tempReading);
    pthread_cancel(thread_platformPost);
    pthread_join(thread_motionSensor, NULL);
    pthread_join(thread_tempReading,  NULL);
    pthread_join(thread_platformPost, NULL);
    printf("Exiting from main thread and process\n");
    exit(0);
}

Upvotes: 8

eerorika
eerorika

Reputation: 238311

So signal only interrupt thread instead of the whole process if there are multi-threading?

Yes. A signal interrupts a single thread of execution. At least according to the POSIX standard. C standard itself doesn't specify behaviour of processes or threads.

If a signal is generated for a multi threaded process as opposed to being generated for particular thread, the signal will be delivered to exactly one of the threads belonging to that process (unless the signal is blocked by all threads).

Upvotes: 1

David Schwartz
David Schwartz

Reputation: 182753

No matter how many threads the process has, some thread catches the signal and runs the signal handler while the other threads (if any) go on about their business. The signal is sent to the process, but what the process does when it gets the signal is dependent on how the process, and the threads in, configured its signal handlers.

The full rules are quite complicated and I think it's safe to say that very few programmers bother to full understand them and generally avoid signals except for very limited purposes. See here for more details.

Upvotes: 2

Related Questions