代码之家  ›  专栏  ›  技术社区  ›  Alex B

recv()在多线程环境中不会被信号中断

  •  16
  • Alex B  · 技术社区  · 15 年前

    我有一根线在一个木块上 recv() 循环,我想终止(假设不能将其更改为 select() 或任何其他异步方法)。

    我还有一个信号处理器 SIGINT 理论上它应该 记录() 错误返回,并且 errno 设置为 EINTR .

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

    这里发生了什么?

    编辑:

    好的,现在我明确地将信号发送给所有阻塞 记录() 线程通过 pthread_kill() 从主线程(导致相同的全局 西京 信号处理程序已安装,但多个调用是良性的)。但是 记录() 呼叫仍未解除阻止。

    编辑:

    我已经写了一个代码示例来重现这个问题。

    1. 主线程将一个套接字连接到一个行为不正常的远程主机,该主机不会让连接中断。
    2. 所有信号都被阻塞。
    3. 读取线程线程已启动。
    4. 主解除锁定并安装的处理程序 西京 .
    5. 读取线程取消阻止并安装的处理程序 SIGUSR1 .
    6. 主线程的信号处理程序发送 SIGUSR1 到读取线程。

    有趣的是,如果我替换 记录() 具有 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)
    
    5 回复  |  直到 11 年前
        1
  •  20
  •   R.. GitHub STOP HELPING ICE    15 年前

    通常信号不会中断系统调用 EINTR . 历史上有两种可能的信号传递行为:BSD行为(系统调用在被信号中断时自动重新启动)和Unix System V行为(系统调用返回-1 errno 设置为 埃因特 当被信号中断时)。Linux(内核)采用了后者,但是GNUC库开发人员(正确地)认为BSD行为要理智得多,在现代Linux系统中,这样做是 signal (这是库函数)导致BSD行为。

    posix允许任何一种行为,因此建议始终使用 sigaction 您可以选择在其中设置 SA_RESTART 根据需要的行为标记或省略它。参见文档 西格玛 在这里:

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

        2
  •  6
  •   bdonlan    15 年前

    在多线程应用中,正常信号可以任意发送到任何线程。使用 pthread_kill 将信号发送到感兴趣的特定线程。

        3
  •  4
  •   blaze    15 年前

    在recv()中等待的同一线程中是否调用了信号处理程序? 您可能需要通过pthread_sigmask()在所有其他线程中显式地屏蔽sigint。

        4
  •  1
  •   sorton9999    15 年前

    正如 ><r. >>在文章中提到的,确实可以更改信号活动。 我经常创建自己的“信号”函数来使用sigaction。这是我用的

    <> <代码> >

    typedef void sigfunc(int);
    
    静态SigFunc*
    _信号(int signum,sigfunc*func)
    {
    结构信号法,OACT;
    
    act.sa_handler=func;
    SigEmptySet(&act.sa_Mask);
    act.sa_flags=0;
    
    如果(信号)!= SIGALRM)
    act.sa_flags=sa_nodefer;//sa_restart;
    
    如果(sigaction(signum&act&oact)<0)
    返回(sig_err);
    返回oact.sa_处理程序;
    }
    < /代码> 
    
    

    上面讨论的属性是sa_flags字段的“或”ing。这是来自“sigaction”的手册页:sa_restart提供了类似于bsd的行为,允许跨信号重新启动系统调用。sa_nodefer意味着允许从其自己的信号处理程序中接收信号。

    当信号调用被替换为“_signal”时,线程被中断。输出输出输出“中断的系统调用”,当sigusr1被发送时,recv返回-1。发送sigint时,程序以相同的输出完全停止,但在结束时调用了abort。

    我没有编写代码的服务器部分,我只是将套接字类型更改为“dgram,udp”,以允许客户端启动。

    在邮件中由编辑到<R..>,确实可以更改信号活动。 我经常创建自己的“信号”函数来使用sigaction。这是我用的

    typedef void Sigfunc(int);
    
    static Sigfunc* 
    _signal(int signum, Sigfunc* func)
    {
        struct sigaction act, oact;
    
        act.sa_handler = func;
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
    
        if (signum != SIGALRM)
            act.sa_flags |= SA_NODEFER; //SA_RESTART;
    
        if (sigaction(signum, &act, &oact) < 0)
            return (SIG_ERR);
        return oact.sa_handler;
    }
    

    上面讨论的属性是sa_flags字段的“或”ing。这是“sigaction”的手册页:sa_restart提供了类似bsd的行为,允许跨信号重新启动系统调用。sa_nodefer意味着允许从自己的信号处理程序中接收信号。

    当信号调用被替换为“_signal”时,线程被中断。输出输出输出“中断的系统调用”,当sigusr1被发送时,recv返回-1。发送sigint时,程序以相同的输出完全停止,但在结束时调用了abort。

    我没有写服务器部分的代码,我只是把套接字类型改为“dgram,udp”,以允许客户机启动。

        5
  •  0
  •   Community Mohan Dere    9 年前

    可以在Linux recv上设置超时: Linux: is there a read or recv from socket with timeout?

    当你收到一个信号时,在做接收的班级上呼叫完成。

    void* signalThread( void* ptr )
    {
        CapturePkts* cap=(CapturePkts*)ptr;
        sigset_t sigSet=cap->getSigSet();
        int sig=-1;
        sigwait(&sigSet,&sig); //signalThread: signal capture thread enabled;
        cout << "signal=" << sig << " caught,ending process" << endl;
        cap->setDone();
        return 0;
    }
    
    class CapturePkts
    {
         CapturePkts() : _done(false) {}
    
         sigset_t getSigSet() { return _sigSet; }
    
         void setDone() {_done=true;}
    
         bool receive( uint8_t *buffer, int32_t bufSz, int32_t &nbytes)
         {
             bool ret=true;
             while( ! _done ) {
             nbytes = ::recv( _sockid, buffer, bufSz, 0 );
             if(nbytes < 1 ) {
                if (errno == EAGAIN || errno == EWOULDBLOCK) {
                   nbytes=0; //wait for next read event
                else
                   ret=false;
             }
             return ret;
         }
    
         private:
         sigset_t _sigSet;
         bool _done;
    };