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

时间:2010-08-27 07:41:14

标签: c linux sockets pthreads signals

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

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

但它没有,我认为这与应用程序是多线程的事实有关。还有另一个线程,同时等待pthread_join()电话。

这里发生了什么?

修改

好的,现在我通过recv()从主线程明确地向所有阻塞pthread_kill()线程传递信号(这导致安装了相同的全局SIGINT信号处理程序,尽管多次调用是良性)。但recv()来电仍未解锁。

修改

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

  1. 主线程将套接字连接到行为不当的远程主机,该主机不允许连接。
  2. 所有信号都被阻止。
  3. 读取线程线程已启动。
  4. 主要取消阻止并安装SIGINT
  5. 的处理程序
  6. 读取线程取消阻止并安装SIGUSR1
  7. 的处理程序
  8. 主线程的信号处理程序向读取线程发送SIGUSR1
  9. 有趣的是,如果我将recv()替换为sleep(),则会被中断。

    PS

    或者,您只需打开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 个答案:

答案 0 :(得分:21)

通常信号不会中断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

答案 1 :(得分:6)

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

答案 2 :(得分:4)

信号处理程序是否在recv()中等待的同一个线程中调用? 您可能需要通过pthread_sigmask()

在所有其他线程中显式屏蔽SIGINT

答案 3 :(得分:1)

正如<R..&gt;中提到的那样,确实可以改变信号活动。 我经常创建自己的“信号”功能,利用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;
}

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字段的'或'。这来自'sigaction'的手册页:SA_RESTART提供类似BSD的行为,允许系统调用跨信号重启。 SA_NODEFER表示允许从其自己的信号处理程序中接收信号。

当信号调用被“_signal”替换时,线程被中断。输出打印出“中断的系统调用”,当发送SIGUSR1时,recv返回-1。发送SIGINT时,程序完全停止使用相同的输出,但最后调用了中止。

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

答案 4 :(得分:0)

您可以在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;
};