当从单独的线程调用时,pselect不会返回信号,但在单线程程序中工作正常

时间:2016-03-16 07:21:48

标签: c multithreading

我正在学习如何使用pselect。我拿了一个示例代码,该代码工作正常并修改它以从一个从main生成的线程调用相同的代码,但它不起作用(pselect仍然永远被阻止)

#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

/* Flag that tells the daemon to exit. */
static volatile int exit_request = 0;

/* Signal handler. */
static void hdl (int sig)
{
    exit_request = 1;
    printf("sig=%d\n", sig);
}

/* Accept client on listening socket lfd and close the connection
 * immediatelly. */
static void handle_client (int lfd)
{
    int sock = accept (lfd, NULL, 0);
    if (sock < 0) {
        perror ("accept");
        exit (1);
    }

    puts ("accepted client");

    close (sock);
}

void *mythread(void *arg  __attribute__ ((unused)))
{
    int lfd;
    struct sockaddr_in myaddr;
    int yes = 1;
    sigset_t mask;
    sigset_t orig_mask;
    struct sigaction act;

    memset (&act, 0, sizeof(act));
    act.sa_handler = hdl;

    /* This server should shut down on SIGUSR1. */
    if (sigaction(SIGUSR1, &act, 0)) {
        perror ("sigaction");
        return NULL;
    }

    sigemptyset (&mask);
    sigaddset (&mask, SIGUSR1);

    if (pthread_sigmask(SIG_BLOCK, &mask, &orig_mask) < 0) {
        perror ("pthread_sigmask");
        return NULL;
    }

    lfd = socket (AF_INET, SOCK_STREAM, 0);
    if (lfd < 0) {
        perror ("socket");
        return NULL;
    }

    if (setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR,
                &yes, sizeof(int)) == -1) {
        perror ("setsockopt");
        return NULL;
    }

    memset (&myaddr, 0, sizeof(myaddr));
    myaddr.sin_family = AF_INET;
    myaddr.sin_addr.s_addr = INADDR_ANY;
    myaddr.sin_port = htons (10000);

    if (bind(lfd, (struct sockaddr *)&myaddr, sizeof(myaddr)) < 0) {
        perror ("bind");
        return NULL;
    }

    if (listen(lfd, 5) < 0) {
        perror ("listen");
        return NULL;
    }

    while (!exit_request) {
        fd_set fds;
        int res;

        /* BANG! we can get SIGUSR1 at this point, but it will be
         * delivered while we are in pselect(), because now
         * we block SIGUSR1.
         */

        FD_ZERO (&fds);
        FD_SET (lfd, &fds);

        res = pselect (lfd + 1, &fds, NULL, NULL, NULL, &orig_mask);
        if (res < 0 && errno != EINTR) {
            perror ("select");
            return NULL;
        }
        else if (exit_request) {
            puts ("exited");
            break;
        }
        else if (res == 0)
            continue;

        if (FD_ISSET(lfd, &fds)) {
            handle_client (lfd);
        }
    }

    return NULL;
}
int main (int argc, char *argv[])
{
    void * res;
    pthread_t mythr_h;
    pthread_create(&mythr_h, (pthread_attr_t *)NULL, mythread, NULL);
    pthread_join(mythr_h, &res);
    return 0;
}

强文

将SIGUSR1发送到此程序后,我发现它在pselect调用中仍然被阻止。当mythread函数中的代码移回main而不是从main生成任何线程时,它完美地工作。

2 个答案:

答案 0 :(得分:1)

  

将SIGUSR1发送到此程序后,我发现它仍然被阻止   pselect电话。当mythread函数中的代码被移回时   进入main而不是从main生成任何线程,它运行得很好。

这是预期的 - 不能保证信号将被传递到“正确”的线程,因为没有明确定义的“正确”线程的概念。

信号和多线程不能很好地混合,但如果你想这样做,我建议摆脱exit_request标志(注意:无论如何都是volatile关键字isn't sufficient to work reliably in multithreaded scenarios),而是创建一对连接的文件描述符(通过调用pipe()函数或socketpair()函数)。你的所有信号处理函数(hdl())需要做的是将一个字节写入两个文件描述符之一。让你的线程在其read-socket-set(fds)中包含另一个文件描述符,这样当写入的字节将导致pselect()返回,然后你对FD_ISSET的后续调用(theSecondFileDescriptorOfThePair,&amp; fds)将返回true ,这就是你的线程将如何知道它现在退出的时间。

答案 1 :(得分:1)

除了pselect()调用上的线程阻塞之外,信号被传递到主线程。如果有多个线程使信号解除阻塞,则可以将信号传递给任何一个线程。

由于您没有指定您的平台,首先我引用POSIX standard(系统接口卷,2.4.1信号生成和传送)。

  

为流程生成的信号应该在流程中正好传递给其中一个在调用sigwait()函数时选择该信号或​​< strong>没有阻止信号传递。

您还可以在Linux联机帮助页signal(7)中看到类似的陈述。

  

可以将过程导向的信号传递给任何人          其中一个当前没有阻塞信号的线程。          如果多个线程的信号未被阻塞,那么          内核选择一个任意线程来传递信号。

和FreeBSD手册页sigaction(2)

  

对于针对该过程的信号,如果是        信号当前未被所有线程阻止,然后传递到        一个没有阻塞它的线程(其选择是        未指定)。

所以你可以做的是阻止SIGUSR1进程中的所有线程,除了调用pselect()的线程。幸运的是,当创建一个新线程时,它会从其创建者继承信号掩码。

从上面的相同POSIX部分

  

线程的信号掩码应该从其父节点或创建线程初始化....

Linux pthread_sigmask(3)

  

新线程继承其创建者信号掩码的副本。

FreeBSD sigaction(2)

  

线程的信号掩码初始化为其父级的信号掩码(通常为空)。

您可以对代码进行以下更改。在main()中,在创建任何线程之前,阻止SIGUSR1。在该主题中,将SIGUSR1未阻止的信号掩码传递给pselect()

--- old.c   Mon Mar 21 22:48:52 2016
+++ new.c   Mon Mar 21 22:53:54 2016
@@ -56,14 +56,14 @@
         return NULL;
     }

-    sigemptyset (&mask);
-    sigaddset (&mask, SIGUSR1);
-
-    if (pthread_sigmask(SIG_BLOCK, &mask, &orig_mask) < 0) {
+    sigemptyset(&orig_mask);
+    if (pthread_sigmask(SIG_BLOCK, NULL, &orig_mask) < 0) {
         perror ("pthread_sigmask");
         return NULL;
     }

+    sigdelset(&orig_mask, SIGUSR1);
+
     lfd = socket (AF_INET, SOCK_STREAM, 0);
     if (lfd < 0) {
         perror ("socket");
@@ -126,6 +126,15 @@
 {
     void * res;
     pthread_t mythr_h;
+    sigset_t mask;
+
+    sigemptyset (&mask);
+    sigaddset (&mask, SIGUSR1);
+
+    if (pthread_sigmask(SIG_BLOCK, &mask, NULL) != 0) {
+        return 1;
+    }
+
     pthread_create(&mythr_h, (pthread_attr_t *)NULL, mythread, NULL);
     pthread_join(mythr_h, &res);
     return 0;

最后一件事不在话题。 printf()不是async-signal-safe函数,因此不应在信号处理程序中调用。