简体   繁体   English

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

[英]pselect does not return on signal when called from a separate thread but works fine in single thread program

I am learning how to use pselect. 我正在学习如何使用pselect。 I took an example code which worked fine and modified it to call the same code from a thread which is spawned from main and it does not work (pselect remains blocked forever) 我以示例代码为例,该代码运行良好,并对其进行了修改,以从从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;
}

strong text 强文本

After sending SIGUSR1 to this program I see that it remains blocked in the pselect call. 将SIGUSR1发送到该程序后,我看到它在pselect调用中仍然处于阻塞状态。 When the code in mythread function is moved back into main and not spawning any thread from main, it works perfectly. 当mythread函数中的代码移回main而不从main产生任何线程时,它可以完美地工作。

After sending SIGUSR1 to this program I see that it remains blocked in the pselect call. 将SIGUSR1发送到该程序后,我看到它在pselect调用中仍然处于阻塞状态。 When the code in mythread function is moved back into main and not spawning any thread from main, it works perfectly. 当mythread函数中的代码移回main而不从main产生任何线程时,它可以完美地工作。

That's to be expected -- there is no guarantee that a signal will be delivered to the "right" thread, since there is no well-defined notion of what the "right" thread would be. 这是预料之中的-由于没有明确定义“正确”线程的含义,因此无法保证将信号传递到“正确”线程。

Signals and multithreading don't mix particularly well, but if you want to do it, I suggest getting rid of the exit_request flag (note: the volatile keyword isn't sufficient to work reliably in multithreaded scenarios anyway), and instead create a connected pair of file descriptors (by calling either the pipe () function or the socketpair () function). 信号和多线程并不能很好地融合在一起,但是如果您想这样做,我建议您摆脱exit_request标志(注意:无论如何,volatile关键字不足以在多线程场景中可靠地工作 ),而是创建一个连接对文件描述符(通过调用pipe ()函数或socketpair ()函数)。 All your signal handler function (hdl()) needs to do is write a byte into one of the two file descriptors. 您的信号处理函数(hdl())所需要做的就是将一个字节写入两个文件描述符之一。 Have your thread include the other file descriptor in its read-socket-set (fds) so that when the byte is written that will cause pselect() to return and then your subsequent call to FD_ISSET(theSecondFileDescriptorOfThePair, &fds) will return true, which is how your thread will know it's time to exit now. 让您的线程在其读套接字集(fds)中包含另一个文件描述符,以便在写入字节时导致pselect()返回,然后您对FD_ISSET(theSecondFileDescriptorOfThePair,&fds)的后续调用将返回true,这您的线程将如何知道现在该退出。

The signal is delivered to the main thread, other than the thread blocking on the pselect() call. 信号传递到主线程,而不是调用pselect()的线程阻塞。 If there are multiple threads that have the signal unblocked, the signal can be delivered to any one of the threads. 如果有多个线程使信号畅通,则可以将信号传递到任何一个线程。

Since you didn't specify your platform, first I'm quoting from the POSIX standard (System Interfaces volume, 2.4.1 Signal Generation and Delivery). 由于您未指定平台,因此首先引用POSIX标准 (系统接口卷,2.4.1信号生成和传递)。

Signals generated for the process shall be delivered to exactly one of those threads within the process which is in a call to a sigwait() function selecting that signal or has not blocked delivery of the signal . 为该过程生成的信号应准确地传递到该过程正在调用sigwait()函数的线程中的一个线程 ,以选择该信号或没有阻止该信号的传递

You can also see similar statements in Linux manpage signal(7) . 您还可以在Linux联机帮助页signal(7)看到类似的语句。

A process-directed signal may be delivered to any one of the threads that does not currently have the signal blocked. 可以将过程控制信号传递到当前未阻塞信号的任何一个线程。 If more than one of the threads has the signal unblocked, then the kernel chooses an arbitrary thread to which to deliver the signal. 如果多个线程中的信号畅通无阻,则内核选择一个任意线程来将信号传递到该线程。

And FreeBSD manpage sigaction(2) . 和FreeBSD的联机帮助页sigaction(2)

For signals directed at the process, if the signal is not currently blocked by all threads then it is delivered to one thread that does not have it blocked (the selection of which is unspecified). 对于定向到该过程的信号,如果该信号当前尚未被所有线程阻塞,则将其传递到一个没有阻塞的线程(未指定选择)。

So what you can do is to block SIGUSR1 for all the threads in the process except for the one that calls pselect() . 因此,您可以做的是阻止进程中所有线程的SIGUSR1调用pselect()的线程。 Luckily when a new thread is created, it inherits the signal mask from its creator. 幸运的是,创建新线程时,它会从其创建者那里继承信号掩码。

From the same POSIX section above, 在上面相同的POSIX部分中,

The signal mask for a thread shall be initialized from that of its parent or creating thread.... 线程的信号掩码应从其父线程或正在创建的线程中初始化。

Linux pthread_sigmask(3) , Linux pthread_sigmask(3)

A new thread inherits a copy of its creator's signal mask. 新线程继承其创建者的信号掩码的副本。

FreeBSD sigaction(2) , FreeBSD sigaction(2)

The signal mask for a thread is initialized from that of its parent (normally empty). 线程的信号掩码从其父线程的信号掩码初始化(通常为空)。

You can make the following changes to your code. 您可以对代码进行以下更改。 In main() , before creating any threads, block SIGUSR1 . main() ,在创建任何线程之前,请阻止SIGUSR1 In the thread, pass a signal mask that has SIGUSR1 unblocked into pselect() . 在线程中,将具有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;

Last thing is off topic. 最后一件事是题外话。 printf() is not an async-signal-safe function, so should not be called in the signal handler. printf()不是async-signal-safe函数,因此不应在信号处理程序中调用它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM