C - SIGINT处理程序不能与多线程一起工作,每个线程都有一个popen进程。

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C - SIGINT处理程序不能与多线程一起工作,每个线程都有一个popen进程。相关的知识,希望对你有一定的参考价值。

我有一个小的C程序,它的功能如下。

  1. 打开多个线程,每一个线程都会产生一个 ssh 流程中使用 popen().
  2. 对于每个线程,处理来自 FILE 回归 popen(). (想想看打开一个 ssh 命令和尾随)。)

我想让它在用户按下 Ctrl+C 一次),所有开 ssh 进程(由线程产生)将被杀死,进程也会相应退出。

我试过一个类似于这里的顶层答案的解决方案。POSIX pthread编程

这个例子是可行的,但是,如果我把线程函数改成和我的一样(呼叫 popen()), 这需要我打 Ctrl+C 多次. 我猜想这和以下事实有关: ssh 进程 popen() 并非无视 SIGINT 信号,允许它通过我定义的处理程序。这样做对吗?

这是我目前的代码。

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void handler(int signo) {
    printf("I'm in the handler with SIGNAL %d!", signo);
    exit(0);
}

void *thread(void *argument) {
    // Hardcoding to "myuser@myhost.com" here for sake of example, but you
    // could image the host is different per thread
    FILE *file = popen("ssh myuser@myhost.com \"tail -f /path/to/some/log.log\"", "r");
    if (file == null) {
        printf("Error opening file\n");
        exit(1);
    }

    char line[2048];
    while (fgets(line, sizeof(line), fp) != NULL) {
        printf("%s\n", line);
    }
    return NULL;
}

int main(void) {
    // Block the SIGINT signal. The threads will inherit the signal mask.
    // This will avoid them catching SIGINT instead of this thread.
    sigset_t sigset, oldset;
    sigemptyset(&sigset);
    sigaddset(&sigset, SIGINT);
    pthread_sigmask(SIG_BLOCK, &sigset, &oldset);

    // Spawn the two threads.
    pthread_t thread1, thread2;
    pthread_create(&thread1, NULL, thread, &(unsigned int){1});
    pthread_create(&thread2, NULL, thread, &(unsigned int){2});

    // Install the signal handler for SIGINT.
    struct sigaction s;
    s.sa_handler = handler;
    sigemptyset(&s.sa_mask);
    s.sa_flags = 0;
    sigaction(SIGINT, &s, NULL);

    // Restore the old signal mask only for this thread.
    pthread_sigmask(SIG_SETMASK, &oldset, NULL);

    // Wait for SIGINT to arrive.
    pause();

    // Cancel both threads.
    pthread_cancel(thread1);
    pthread_cancel(thread2);

    // Join both threads.
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    // Done.
    puts("Terminated.");
    return EXIT_SUCCESS;
}

我对发生的事情的假设是否正确?如果不正确,有什么想法我做错了吗?

答案

当你启动ssh程序时,你是在屏蔽SIGINT的情况下启动的,所以那些在管道末端的程序会忽略SIGINT。你可以通过将popen命令改为类似 "sleep 30; ls "的命令来测试这一点,启动你的程序,按ctrl-C键,然后输入 "ps s"。 你很可能会看到几行 "sh -c sleep 30; ls "和 "sleep 30 "这样的形式,以及他们的信号处置.如果你等到他们自然死亡,然后再次运行程序,但这次用 "Ctrl-\"打它。(SIGTERM),你会注意到它会立即清理。

你可以将popen行改为。

sigset_t nset;
pthread_sigmask(SIG_SETMASK, &oldset, &nset);
FILE *file = popen("ssh myuser@myhost.com \"tail -f /path/to/some/log.log\"", "r");
pthread_sigmask(SIG_SETMASK, &nset, NULL);

并把oldset的定义移到普通的位置。大多数情况下,你会得到你想要的效果,但是在这两个对pthread_sigmask()的调用之间有一个竞赛条件,一个SIGINT可能会在这个上下文中调用handler,而不是预期的那个。

要解决这个竞赛条件,你需要做的是写一个类似于popen()的函数,fork(2)然后在exec(2)调用shell之前,将子函数中的信号设置成你想要的样子。

这很不幸,但信号和线程真的不好混。

以上是关于C - SIGINT处理程序不能与多线程一起工作,每个线程都有一个popen进程。的主要内容,如果未能解决你的问题,请参考以下文章

cProfiler 与多处理一起工作很奇怪

为什么多线程通常与多处理相结合?

异常与多线程

与多处理一起使用时,PyTesseract 调用工作非常缓慢

C - 在 SIGINT 上释放分配的内存

C++ 信号处理 signal(SIGINT, signalHandler);