使用 pthread 时无法捕获 SIGINT 信号

Posted

技术标签:

【中文标题】使用 pthread 时无法捕获 SIGINT 信号【英文标题】:Not able to catch a SIGINT signal while using pthreads 【发布时间】:2016-04-05 14:19:36 【问题描述】:

我制作了一个聊天服务器,它使用多线程来处理多个客户端。我有一个循环无限运行并等待新客户。我想在按 ctrl+c 后摆脱它。所以,我正试图捕捉here 中提到的 SIGINT 信号。但我无法退出该程序。我正在 Linux 上的终端中工作。

服务器.c

//for running type ./a.out anyportnumber
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
int s2;
int arr[100];
int tc = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
volatile sig_atomic_t flag = 1;
void handler(int signal)

    flag = 0;

void sendtoall(char *msg,int s1)

    int i;
    pthread_mutex_lock(&mutex);
    for(i = 0; i < tc; i++) 
        if(arr[i] != s1) 
            write(arr[i],msg,strlen(msg));
    
    pthread_mutex_unlock(&mutex);

void *function(void *s)

    int s1;
    int n;
    char rmsg[500];
    s1 = *(int *)s;
    while((n = read(s1,rmsg,500)) > 0) 
        rmsg[n] = '\0';
        sendtoall(rmsg,s1);
        bzero(rmsg,500);
    
    pthread_exit(NULL);

int main(int arrc,char *argv[])

    struct sockaddr_in server,client;
    int s1,len;
    int n;
    int port;
    pthread_t t1;
    char message[500];
    port = atoi(argv[1]);
    bzero((char *)&server,sizeof(server));
    server.sin_port = htons(port);
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_family = AF_INET;
    s1 = socket(AF_INET,SOCK_STREAM,0);
    if(s1 == -1) 
        perror("socket not created\n");
        exit(1);
    
    if(bind(s1,(struct sockaddr *)&server,sizeof(struct sockaddr)) == -1) 
        perror("socket not binded\n");
        exit(1);
    
    if(listen(s1,5) == -1) 
        perror("unable to listen");
        exit(1);
    
    len = sizeof(struct sockaddr_in);
    signal(SIGINT, handler);
    while(flag) 
        s2 = accept(s1,(struct sockaddr *)&client,&len);
        pthread_create(&t1,NULL,function,(void *)&s2);
        arr[tc] = s2;
        tc++;
    
    close(s1);
    close(s2);
    return 0;


【问题讨论】:

我在 linux 上,在终端上使用 ctrl+c。 您的代码运行良好。我正在使用 3.19.0-33-generic 版本的 linux。 我在与线程关联的函数中多了一个while循环。 调试器也可能有帮助(例如gdb)。 Fixing race condition when sending signal to interrupt system call的可能重复 【参考方案1】:

通过中断处理程序设置的标志捕获信号不适用于信号需要可靠地中断阻塞系统调用的情况(在您的情况下为accept)。问题是信号可能在进入阻塞系统之前到达:在检查标志之后但在信号中断给定系统调用的状态之前。因此,即使设置了标志,系统调用也会阻止程序的执行。

另外,当多个线程允许信号时,只有一个线程捕获信号,并且未指定它们中的哪一个。在您的情况下,主线程可能没有捕获信号,因此 accept 根本不会被中断。

虽然第二个问题(与多线程程序有关)很容易通过阻塞除主线程之外的所有线程中的信号来解决,但第一个问题需要特殊的方法。可能的:

signalfd() 结合 poll() / select()

最复杂的方法,但几乎适用于所有情况。信号被“转换”为文件描述符,它与系统调用等待的文件描述符结合在一起。结果文件描述符集用于轮询

// Preparations
sigset_t s;
sigemptyset(&s);
sigaddset(&s, SIGINT);
sigprocmask(SIGBLOCK, &s, NULL); // For multithreaded program *pthread_sigmask* should be used.
int fd_int = signalfd(0, &s, 0); // When signal arises, this file becomes readable

// Usage
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd_int, &fds);
FD_SET(s1, &fds);
int nfds = MAX(fd_int, s1) + 1;
select(nfds, &fds, NULL, NULL, NULL);
if(FD_ISSET(fd_int, &fds)) 
    // Signal is arrived
    ...

else 
    // New connection request is received
    accept(s1, ...);
    ...

请注意,该信号对所有线程都被阻止,包括主线程。

在信号处理程序内部完成并退出

最简单的方法,但使用有限。如果所有终结操作都是信号安全的(请参阅man singnal(7) 了解信号处理程序中允许调用的函数的完整列表),则该操作可能由信号处理程序本身执行,然后退出程序:

void handler(int signal)

    close(s1);
    close(s2);
    _exit(0); // This function is thread-safe, unlike to *exit*.

但在多线程程序的情况下,这种方法通常不适合,因为函数thread_join 不是信号安全的。

在信号处理程序中改变系统调用参数的状态

所以系统调用会立即返回,不会阻塞。最简单的状态更改是关闭系统调用使用的文件描述符:

void handler(int signal)

    flag = 0;
    close(s1); // Close file descriptor which is used by the system call.


while(flag)

    s2 = accept(s1, ...);
    if(s2 == -1 && !flag) 
        // Signal is catched
        break;
    
    ...

注意,在多线程程序的情况下,除了主线程之外的所有线程都应该明确阻止信号。否则,在一个线程中关闭文件,而其他线程读取它不需要中断读取线程。

另外,在多线程程序的情况下,应该考虑到,如果某个 other 线程创建(打开)文件描述符,它可以重用一个,在信号处理程序中关闭,就在它之前在系统调用中使用。

【讨论】:

在这样的信号处理程序中调用close() 会引入竞争:文件描述符的下一个分配将使用刚刚关闭的文件描述符编号。您的accept() 可能会在其他人的套接字、管道、文件、消息队列描述符等上调用。 @pilcrow:关于可能重用已关闭文件描述符的注意事项,但在给定的情况下,这几乎不是问题。首先,在给定的示例中,线程实际上并没有创建任何文件描述符。其次,即使这样做,它也应该为accept() 创建基于连接的侦听套接字 可能会造成任何伤害:对于任何其他类型的文件描述符accept() 什么都不做并返回错误(EINVAL, ENOSOCK 或 EOPNOTSUPP)。 为了完全消除closeaccept之间的争用文件描述符分配,而不是close(s1)信号处理程序可以使用dup2(oldfd, s1),其中oldfd是任何已经打开的文件描述符,但是不是适合accept() 的套接字。所以s1原子地“重新链接”到与oldfd 相同的对象,accept(s1) 将返回适当的错误而不会造成伤害。

以上是关于使用 pthread 时无法捕获 SIGINT 信号的主要内容,如果未能解决你的问题,请参考以下文章

无法在消费者线程中使用 JNI 的生产者-消费者程序中捕获 SIGINT 信号

SDL/C++ OpenGL 程序,如何阻止 SDL 捕获 SIGINT

使用 KeyboardInterrupt 异常捕获 SIGINT 在终端中有效,而不是在脚本中

使用pthread时,字节仍可访问

如何在 C# 控制台应用程序中捕获 ctrl-c (SIGINT)

向守护进程发送 SIGINT