【问题标题】:signal handler and multithreading信号处理程序和多线程
【发布时间】:2018-12-19 11:22:21
【问题描述】:

我知道信号是 CPU 和操作系统内核之间的一种通信方式。信号可以中断指令序列,执行处理程序并返回到指令序列。

这里是对这个link的一段描述:

当一个被捕获的信号被一个进程处理时,正常的 进程正在执行的指令序列是暂时的 被信号处理程序中断。然后该过程继续 正在执行,但信号处理程序中的指令现在是 执行。如果信号处理程序返回,则该过程继续 执行它正在执行的正常指令序列 信号被捕获。

这是我的测试:

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

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

我执行它,如果我按ctrl+C,将打印int,然后我必须等待5秒,无论我在等待5秒时按了多少次ctrl+C,都没有任何反应。

然后我再做一个测试:

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;
}

对于这段代码,我发现我可以连续按三下ctrl+C,会打印三个int,然后我需要等待大约5秒。

所以看起来第一个ctrl+C中断了t1线程,第二个中断了t2线程,第三个中断了主线程。

如果有多线程,那么信号只中断线程而不是整个进程?

【问题讨论】:

  • 您的代码调用了未定义的行为。每footnote 183 of the C standard:“因此,信号处理程序通常不能调用标准库函数。” POSIX allows the calling of async-signal-safe functions from within a signal handler。使用cout 和调用sleep() 不是异步信号安全的。
  • 你不想这样处理信号。从来没有。
  • @AndrewHenle 是的,你是对的。但是我需要打印一些东西来调试,所以我认为我别无选择。在信号处理程序中使用不可重入函数可能是未定义的行为。至少对于上面的代码,它可以按预期工作。
  • @Yves 在 POSIX 系统上:write( STDOUT_FILENO, buffer, strlen( buffer ) );STDERR_FILENO,如果您愿意。构建一个 C 风格的字符串,然后使用write()

标签: c++ c linux multithreading signals


【解决方案1】:

简介

首先,每个线程都有自己的掩码,指定它正在侦听的信号。创建线程时,它会继承创建它的线程的掩码(让我称之为父线程),当调用pthread_create 时它是活动的。

一般来说,如果一个线程正在监听信号,那么其他线程不应该监听信号会更好,除非您希望有多个线程执行相同的操作(例如,在服务器中处理连接请求以同时处理多个请求时) )。通过这种方式,您始终知道哪个线程正在处理信号。否则你不知道哪个线程正在接收信号以及程序的哪个部分正在执行:调试变得不可能(一个例子是你自己的问题)。

更改从父线程创建的子线程的掩码,您可以创建一个新掩码,请将其设置为活动状态,使用pthread_create 创建一个新线程,然后在父线程中再次激活之前的掩码(参见答案末尾的代码)。

编辑:根据this post,最好使用sigaction() 而不是signal。在现代系统中signal() 是用sigaction() 实现的,所以应该没有任何区别。但是,如果使用它的旧实现,可能会出现问题。

回答

因此,如果有信号,则仅中断线程而不是整个进程 是多线程吗?

NO:信号只是信号,它们不做任何事情。与信号相关的动作有能力做一些事情,包括停止程序或终止线程。每个信号都有一个关联的默认操作,SIGINT 的默认操作是中断进程。

使用您的处理程序,您将覆盖默认操作。因此它不会再停止程序,但会执行您在线程函数中指定的操作。

在第一种情况下,您只有一个线程,即主线程,这是一个无限循环,只要他还活着,它就会始终捕获信号,这就是行为的原因。如果您重新发送信号,则信号会暂时被阻塞,直到信号处理程序结束执行。然而,如果在处理程序执行时发送了许多信号,您可能会丢失一些信号。 事实上,正如here 所解释的那样,阻塞信号被设置为待处理,但没有排队。术语 pending 意味着操作系统通过设置一个标志来记住有一个信号等待在下一个机会被传递,而 not queued 意味着它通过在某处设置标志,但不是通过准确记录到达的信号数量。 因此,如果信号被发送一次、5 次或更多(尝试在您的程序中多次按 CTRL+C:我已经尝试过),而信号处理程序正在执行它会产生完全相同的行为。

在第二种情况下,您有 3 个线程:main 一个、t1t2:它们都可以看到信号 SIGINT,并且它们都关联了相同的信号处理程序。如果您一个接一个地按 3 次,它们三个都将执行处理程序:这就是您看不到任何延迟的原因。如果您按非常非常快并且超过 3 次(侦听该信号的线程数),我认为您会看到类似于第一个行为的内容。

我将以the code I posted in a question 结束我的回答,我在其中设置了掩码,以便某些信号仅被主线程捕获:

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);
}

【讨论】:

  • 您的答案+1。这里有一些改进的想法:提到 async-signal-safe getpid(2)pthread_self 函数来了解正在调用的线程处理程序;提到 Ctrl-C 通常由 shell、进程组和具有负 PID 的 kill(2) 函数处理。
  • 谢谢...我会检查他们,看看我是否可以改进它。
【解决方案2】:

无论进程有多少线程,一些线程会捕获信号并运行信号处理程序,而其他线程(如果有)则继续他们的业务。信号被发送到进程,但进程在收到信号时会做什么取决于进程和其中的线程如何配置其信号处理程序。

完整的规则非常复杂,我认为可以肯定地说,很少有程序员会费心去完全理解它们,并且通常会避免信号,除非是非常有限的目的。详情请见here

【讨论】:

    【解决方案3】:

    如果有多线程,那么信号只中断线程而不是整个进程?

    是的。信号中断单个执行线程。至少根据 POSIX 标准。 C 标准本身并没有指定进程或线程的行为。

    如果信号是为多线程进程生成的,而不是为特定线程生成的,则该信号将准确传送到属于该进程的线程之一(除非该信号被所有线程阻塞)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-01-24
      • 2011-11-18
      • 2021-12-21
      • 1970-01-01
      • 2014-08-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多