【问题标题】:What happens to threads when process receives signal?当进程收到信号时线程会发生什么?
【发布时间】:2020-07-16 00:46:29
【问题描述】:

我是 C 新手,所以问题可能很愚蠢。

我正在编写操作从客户端发送的数据的套接字服务器。 当它获得连接时,它会创建将处理请求的线程。

它还定义了信号处理程序。当收到 SIGUSR1 时,它会将调试信息打印到日志文件中。 问题是:信号处理函数运行时线程会发生什么?

如果线程继续运行它们的工作,有没有办法在调试处理程序工作时冻结它们?

我试图在 pthreads(7) 的联机帮助页中找到答案,但此时不明白。 抱歉,可能有不正确的术语。

【问题讨论】:

    标签: gcc pthreads signals


    【解决方案1】:

    当收到 SIGUSR1 时,它会将调试信息打印到日志文件中。问题是:信号处理函数运行时线程会发生什么?

    进程中的一个线程(通常是 Linux 上的主线程 - 但这不是保证或可移植的假设)处理信号,而其他线程继续运行1

    如果线程继续运行它们的工作,有没有办法在调试处理程序工作时冻结它们?

    没有直接的方法可以实现这一目标。您可以实现一种机制,例如,处理 SIGUSR1 的线程通知其他线程等待(例如,使用条件变量),然后一旦处理了信号,它就可以通知其他线程继续。但我不建议这样做,因为信号处理程序应该尽可能地做小的功能。在信号处理程序中具有复杂的功能通常被认为是不好的。相反,您应该重新设计,以便其他线程不必停止(如果在记录调试信息时其他线程继续运行,会出现什么问题?)。

    1 处理这个问题的常用方法是使用专门的线程来处理信号。即阻止您感兴趣的信号并创建一个处理这些信号的线程(创建其他线程之前)。 pthread_sigmask(3) 有一个如何做的例子。

    -

    【讨论】:

    • 这种奇怪要求的原因很简单:处理程序将有关运行线程的调试信息写入日志文件。在大多数情况下,线程的生命周期很短。而且这种概率非常大的调试机制不会去生产。只有在开发阶段才能了解过程。
    • 您可以有一个处理 SIGUSR1 的专用线程(这在任何情况下都是必需的 - 请参阅问题中的链接)。当 SIGUSR1 到达时,这个线程只是暂停(和取消暂停)所有其他线程 - 就像在 this answer 中一样。这样,日志写入就在信号处理程序上下文之外进行。
    • 此外,(1)您需要决定在前一个信号的处理(即日志写入)完成之前,当进一步的 SIGUSR1 信号到达时要做什么。通常你会想进一步忽略 SIGUSR1 的 (2) 当其他可能有不同处理程序的信号到达时(例如 SIGINT 或 SIGTERM)要做什么。 (2) 如果您被它们打扰,可能不是问题(例如,如果 SIGTERM 到达,您可能会让进程终止 - 默认处置)。
    【解决方案2】:

    线程和信号的一些介绍 (signal(7))

    • 信号处置是每个进程的

    信号处置是每个进程的属性:在多线程应用程序中,特定信号的处置对所有线程都是相同的。

    • 信号可以是进程导向的或线程导向的

    进程导向的信号:进程导向的信号是针对整个进程(并因此等待)的信号。进程导向的信号可以传递给当前没有阻塞信号的任何一个线程。如果多个线程的信号被解除阻塞,那么内核会选择一个任意线程来传递信号。

    线程导向信号:线程导向信号是针对特定线程的信号。该集合将由未决的进程导向信号集和调用线程的未决信号集的并集组成。

    • 异步和同步信号处理

    您可以配置您的程序来告诉如何处理信号。你可以忽略它们(很少有不能忽略的),注册一个信号处理程序,当接收到特定信号时将调用它(asynchronous),或者阻止它稍后处理它(synchronous)。

    来到你的案子,

    “问题是:信号处理函数运行时线程会发生什么?”

    信号被传递给配置为接收它的任何线程一次。异步处理信号的线程停止它正在做的任何事情并跳转到配置的信号处理程序。其余线程中的执行流程不受影响。

    如果线程继续运行它们的工作,有没有办法在调试处理程序工作时冻结它们?

    没有标准的方法来做到这一点。您需要建立自己的机制来实现这一点。

    为了进一步研究,需要明确调试处理程序的执行位置。在每个线程或main() 或特定线程中?

    编辑

    假设main() 实现了日志功能,下面尝试实现相同的基本最小值。添加了注释,使您能够遍历代码并理解实现。

    #define THREAD_MAX_COUNT 100
    
    #include <pthread.h>
    #include <semaphore.h>
    #include <signal.h>
    #include <sys/signalfd.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    int debug;
    sigset_t debug_mask;
    pthread_t main_tid;
    
    void* thread_func(void* th_data)
    {
        /* .... */
    
        for ( ; ; ) {
    
            if (debug) {    // If debug procedure starts
                printf("Freezing %d\n", *((int*) th_data));
    
                pthread_kill(main_tid, SIGRTMIN);  // Notify the main thread about the thread's freeze.
                int signo;
                sigwait(&debug_mask, &signo);  // Wait till logging is done. main() will signal once it is done. 
    
                printf("Resuming %d\n", *((int*) th_data));
            }
    
            /* ... */
        }
    
        return NULL;
    }
    
    int main() {
    
        /* Block SIGINT SIGRTMIN*/
    
        sigset_t sigmask;
        sigemptyset(&sigmask);
        sigaddset(&sigmask, SIGINT);
        sigaddset(&sigmask, SIGRTMIN);
    
        pthread_sigmask(SIG_BLOCK, &sigmask, NULL);
    
        /* Set debug variables */
    
        debug = 0;
        sigemptyset(&debug_mask);
        sigaddset(&debug_mask, SIGRTMIN);
        main_tid = pthread_self();
    
        /* Get signalfd for SIGINT */
    
        int sigfd = signalfd(-1, &sigmask, 0);
        struct signalfd_siginfo sigbuf;
    
        /* Select variable initializations */
    
        fd_set rd_set, tr_set;
        FD_ZERO(&rd_set);
        FD_SET(sigfd, &rd_set);
    
        int td_count = 0;
        pthread_t tids[THREAD_MAX_COUNT];
    
        for ( ; ; ) {
            /* Wait for signal */
            tr_set = rd_set;
    
            select(sigfd + 1, &tr_set, NULL, NULL, NULL);
    
            if (FD_ISSET(sigfd, &tr_set)) {
                /* Read the pending signal */
                read(sigfd, &sigbuf, sizeof(sigbuf));
    
                /* Start logging */
                debug = 1;
    
                int signo;
                for (int count = 0; count < td_count; count++) {
                    /* Wait for all threads to freeze */
                    sigwait(&debug_mask, &signo);
                }
    
                printf("Logging...\n");
                sleep(3);
    
                /* End logging and resume threads */
                debug = 0;
    
                for (int count = 0; count < td_count; count++)
                    pthread_kill(tids[count], SIGRTMIN);
    
                /* Note below code is for testing purpose; Creates new thread on each interruption */
                int* td_data = malloc(sizeof(int));
                *td_data = td_count;
    
                pthread_create(tids + td_count, NULL, thread_func, td_data);
    
                td_count++;
            }
        }
    
        return 0;
    }
    

    终端会话:

    $ gcc SO.c -lpthread 
    $ ./a.out 
    ^CLogging...
    ^CFreezing 0
    Logging...
    Resuming 0
    ^CFreezing 0
    Freezing 1
    Logging...
    Resuming 0
    Resuming 1
    ^CFreezing 0
    Freezing 1
    Freezing 2
    Logging...
    Resuming 1
    Resuming 0
    Resuming 2
    ^CFreezing 2
    Freezing 3
    Freezing 1
    Freezing 0
    Logging...
    Resuming 1
    Resuming 3
    Resuming 2
    Resuming 0
    ^CFreezing 1
    Freezing 4
    Freezing 3
    Freezing 0
    Freezing 2
    Logging...
    Resuming 1
    Resuming 2
    Resuming 0
    Resuming 4
    Resuming 3
    ^CFreezing 3
    Freezing 0
    Freezing 4
    Freezing 2
    Freezing 5
    Freezing 1
    Logging...
    Resuming 0
    Resuming 1
    Resuming 2
    Resuming 5
    Resuming 3
    Resuming 4
    ^\Quit (core dumped)
    

    【讨论】:

    • handler在main()中注册如下signal(SIGUSR1, info_handler);
    • @AntonZakharov 你在哪个线程中执行它并不重要。因为所有过程对于该特定信号都将具有相同的信号处置。问题是您想要执行该信号处理程序的哪个线程?主线程或为调试信息注册的每个线程或某些特定线程。
    • 当我回答 P.P 时,我想在主线程中收集调试信息,因为大部分时间它都在等待连接,因此是空闲的。调试信息对应于处理请求的正在运行的线程。据我了解,这个问题没有简单的解决方案。或许以另一种方式达到目标会更好。
    • @AntonZakharov 不,你不需要改变你的实现思路。在某些项目中,它可能需要这样的实现。接受挑战并找到周围的黑客。我编写了一个这样的hack。应对挑战很有趣。谢谢:)
    • 非常感谢您花时间回答这个问题!
    猜你喜欢
    • 1970-01-01
    • 2019-06-05
    • 1970-01-01
    • 2020-02-27
    • 2021-12-13
    • 2013-09-11
    • 2016-05-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多