【问题标题】:Non-blocking check for signals in a loop循环中的信号的非阻塞检查
【发布时间】:2012-01-09 15:01:43
【问题描述】:

我在应用程序中有一个线程,它有这样的循环:

...
while (1)
{
    checkDatabase();
    checkChildren();

    sleep(3);
}
...

checkDatabase() 是不言自明的; checkChildren() 只是调用 waitpid(-1, &status, WNOHANG) 来处理已经退出或收到信号的子进程。

该应用程序运行良好,但它具有默认的信号处理。问题是这个父进程有许多线程(现在不要担心子进程)而且我对同步信号没有任何经验,更不用说在 POSIX 线程应用程序中了。我以前用过signal(),但显然它是不可移植的,而且它也不能满足我的需要。我完全没有使用sigaction 方法的经验,也找不到关于如何填写结构等的好的文档。

我需要做的是在上述循环中同步捕获SIGINTSIGTERMSIGQUIT 等终止信号(并且我需要完全忽略SIGPIPE 以便我可以捕获EPIPE 错误来自 IO 方法),所以它看起来像这样:

...
while (1)
{
    checkDatabase();
    checkChildren();
    checkForSignals();

    sleep(3);
}
...

所有其他线程不应该与信号有任何关系;只有执行这个循环的线程应该知道它。而且,很明显,它需要是一个非阻塞检查,这样循环在它的第一次迭代中就不会阻塞。如果找到信号,则调用的方法将整理其他线程并销毁互斥锁​​,等等。

谁能给我一个提示?非常感谢。

【问题讨论】:

  • synchronous signals,您的意思是您希望避免注册信号处理程序?
  • 是的,我认为它会组织得更好,也更安全,因为我知道某些调用在异步信号处理程序中是不安全的。我猜这是最好的方法,但我不是 100% 确定。其中一个问题还与 ANSI 信号处理不可移植有关,尽管这不是主要问题。

标签: c linux pthreads signals


【解决方案1】:

(根据问题的 cmets,为了完整起见,此解决方案尝试避免使用信号处理程序。)

可以阻止信号通过sigprocmask()(或者,更确切地说,pthread_sigmask(),因为您正在使用线程)引发信号。从那时起,通过sigpending() 可以获得被提出但被阻止的信号。

因此,您可以执行以下操作(为简洁起见,省略错误检查):

sigset_t blocked;
sigemptyset(&blocked);
sigaddset(&blocked, SIGINT);
sigaddset(&blocked, SIGTERM);
sigaddset(&blocked, SIGQUIT);
pthread_sigmask(SIG_BLOCK, &blocked, NULL); // Block SIGINT, SIGTERM and SIGQUIT.
signal(SIGPIPE, SIG_IGN);                   // Ignore SIGPIPE.

然后,稍后:

void checkForSignals(void)
{
    sigset_t pending;
    sigpending(&pending);
    if (sigismember(&pending, SIGINT)) {
        // Handle SIGINT...
    }
    if (sigismember(&pending, SIGTERM)) {
        // Handle SIGTERM...
    }
    if (sigismember(&pending, SIGQUIT)) {
        // Handle SIGQUIT...
    }
}

由于sigpending() 没有阻止,这似乎符合您的要求。

【讨论】:

  • 感谢您的宝贵时间。据推测,在第一个 sn-p 之后创建的子进程将继承信号掩码和东西,所以我需要调用 signal(sig, SIG_DFL) 来撤消 sigmask 的东西吗?
  • 信号掩码确实会被继承,你必须用SIG_SETMASK和一个空的sigset_t(通过调用sigemptyset()而不是sigaddset())在孩子中调用pthread_sigmask()恢复默认行为的过程。
  • 好的,谢谢。还有一件事,什么时候应该调用第一个 sn-p ?在创建线程之前还是之后?
  • 在主线程上调用一次更容易,然后再创建其他线程,以便它们继承设置。否则,您将不得不为每个线程调用它。
【解决方案2】:

使用相同的函数为SIGINTSIGTERMSIGQUIT 创建一个信号处理程序。在该信号函数中,只需设置一个可以在循环中轮询的标志。

类似这样的:

/* Global variable, will be set to non-zero if SIGINT, SIGTERM or SIGQUIT is caught */
int term_signal_set = 0;

void my_signal_handler(int)
{
    term_signal_set = 1;
}

/* ... */

signal(SIGINT, my_signal_handler);
signal(SIGTERM, my_signal_handler);
signal(SIGQUIT, my_signal_handler);
signal(SIGPIPE, SIG_IGN);  /* So functions return EPIPE */

while (1)
{
    /* ... */

    if (term_signal_set > 0)
        break;  /* Or do something else */

    sleep(3);
}

【讨论】:

  • 这看起来非常简单,但看起来尽管是异步的,但它仍然可以工作。我如何确保子进程(与父线程相反)不会模仿这一点?我在孩子的开头使用signal(signal, SIG_DFL),对吗?另外,请注意我对上述问题的评论。谢谢。
【解决方案3】:

在接收信号的多线程应用程序中,没有预先确定哪个线程接收信号。典型的解决方法包括在信号处理程序中设置一个全局变量并从专用线程检查它。

因此,在您的情况下,信号处理程序(从任何线程调用)只会为接收到的信号设置类似于全局变量的东西,并且在 CheckForSignals() 中您将对其进行测试。

【讨论】:

    【解决方案4】:

    sigaction 是要走的路。 man sigaction 应该可以帮助你。这是一个来自网络的例子

    #include <stdio.h>
    #include <signal.h>
    #include <string.h>
    #include <unistd.h> 
    
    struct sigaction act;
    
    void sighandler(int signum, siginfo_t *info, void *ptr)
    {
        printf("Received signal %d\n", signum);
        printf("Signal originates from process %lu\n",
            (unsigned long)info->si_pid);
    }
    
    int main()
    {
        printf("I am %lu\n", (unsigned long)getpid());
            memset(&act, 0, sizeof(act));
    
        act.sa_sigaction = sighandler;
        act.sa_flags = SA_SIGINFO;
    
        sigaction(SIGTERM, &act, NULL);
            // Waiting for CTRL+C...
        sleep(100);  
        return 0;
    }
    

    【讨论】:

    • 您不应在信号处理程序中使用 printf(和其他不可重入函数,例如 malloc)。在这个演示程序中,它看起来无害,但是不要在工作中尝试这个
    猜你喜欢
    • 2022-08-20
    • 1970-01-01
    • 2018-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多