我有一个处于阻塞的recv()循环中的线程,我想终止(假设不能将其更改为select()或任何其他异步方法)。

我也有一个捕获SIGINT的信号处理程序,从理论上讲,它应该使recv()返回错误,并将errno设置为EINTR

但事实并非如此,我认为这与应用程序是多线程的事实有关。还有另一个线程,正在等待pthread_join()调用。

这里发生了什么事?

编辑:

好的,现在我通过主线程中的recv()将信号显式传递给所有阻塞的pthread_kill()线程(这导致安装了相同的全局SIGINT信号处理程序,尽管多次调用是良性的)。但是recv()调用仍然没有被解除阻止。

编辑:

我写了一个代码示例来重现该问题。

  • 主线程将套接字连接到行为异常的远程主机,该主机不会断开连接。
  • 所有信号均被阻止。
  • 读取线程线程已启动。
  • Main取消阻止并安装SIGINT的处理程序。
  • 读取线程取消阻塞并为SIGUSR1安装处理程序。
  • 主线程的信号处理程序将SIGUSR1发送到读取线程。

  • 有趣的是,如果我将recv()替换为sleep(),它会被打断。

    聚苯乙烯

    或者,您可以仅打开UDP套接字而不使用服务器。

    客户
    #include <pthread.h>
    #include <signal.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <memory.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/tcp.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <errno.h>
    
    static void
    err(const char *msg)
    {
        perror(msg);
        abort();
    }
    
    static void
    blockall()
    {
        sigset_t ss;
        sigfillset(&ss);
        if (pthread_sigmask(SIG_BLOCK, &ss, NULL))
            err("pthread_sigmask");
    }
    
    static void
    unblock(int signum)
    {
        sigset_t ss;
        sigemptyset(&ss);
        sigaddset(&ss, signum);
        if (pthread_sigmask(SIG_UNBLOCK, &ss, NULL))
            err("pthread_sigmask");
    }
    
    void
    sigusr1(int signum)
    {
        (void)signum;
        printf("%lu: SIGUSR1\n", pthread_self());
    }
    
    void*
    read_thread(void *arg)
    {
        int sock, r;
        char buf[100];
    
        unblock(SIGUSR1);
        signal(SIGUSR1, &sigusr1);
        sock = *(int*)arg;
        printf("Thread (self=%lu, sock=%d)\n", pthread_self(), sock);
        r = 1;
        while (r > 0)
        {
            r = recv(sock, buf, sizeof buf, 0);
            printf("recv=%d\n", r);
        }
        if (r < 0)
            perror("recv");
        return NULL;
    }
    
    int sock;
    pthread_t t;
    
    void
    sigint(int signum)
    {
        int r;
        (void)signum;
        printf("%lu: SIGINT\n", pthread_self());
        printf("Killing %lu\n", t);
        r = pthread_kill(t, SIGUSR1);
        if (r)
        {
            printf("%s\n", strerror(r));
            abort();
        }
    }
    
    int
    main()
    {
        pthread_attr_t attr;
        struct sockaddr_in addr;
    
        printf("main thread: %lu\n", pthread_self());
        memset(&addr, 0, sizeof addr);
        sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (socket < 0)
            err("socket");
        addr.sin_family = AF_INET;
        addr.sin_port = htons(8888);
        if (inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr) <= 0)
            err("inet_pton");
        if (connect(sock, (struct sockaddr *)&addr, sizeof addr))
            err("connect");
    
        blockall();
        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
        if (pthread_create(&t, &attr, &read_thread, &sock))
            err("pthread_create");
        pthread_attr_destroy(&attr);
        unblock(SIGINT);
        signal(SIGINT, &sigint);
    
        if (sleep(1000))
            perror("sleep");
        if (pthread_join(t, NULL))
            err("pthread_join");
        if (close(sock))
            err("close");
    
        return 0;
    }
    

    服务器
    import socket
    import time
    
    s = socket.socket(socket.AF_INET)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(('127.0.0.1',8888))
    s.listen(1)
    c = []
    while True:
        (conn, addr) =  s.accept()
        c.append(conn)
    

    最佳答案

    通常,信号不会使用EINTR中断系统调用。从历史上看,有两种可能的信号传递行为:BSD行为(当被信号中断时,系统调用会自动重新启动)和Unix System V行为(当信号被中断时,系统调用会返回-1,且errno设置为EINTR)。 Linux(内核)采用了后者,但是GNU C库开发人员(正确地)认为BSD行为更为合理,因此在现代Linux系统上,调用signal(这是一个库函数)会导致BSD行为。

    POSIX允许任何一种行为,因此建议始终使用sigaction,您可以在其中选择设置SA_RESTART标志或根据所需的行为将其忽略。请在此处查看sigaction的文档:

    http://www.opengroup.org/onlinepubs/9699919799/functions/sigaction.html

    07-26 09:41
    查看更多