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

Ale*_*x B 17 c sockets linux signals pthreads

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

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

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

这里发生了什么事?

编辑:

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

编辑:

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

  1. 主线程将套接字连接到不会让连接进入的行为不当的远程主机.
  2. 所有信号都被封锁
  3. 读线程线程已启动.
  4. 主要解锁和安装处理程序SIGINT.
  5. 读线程解锁并安装处理程序SIGUSR1.
  6. 主线程的信号处理程序将a发送SIGUSR1到读取线程.

有趣的是,如果我取代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;
}
Run Code Online (Sandbox Code Playgroud)

服务器

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)
Run Code Online (Sandbox Code Playgroud)

R..*_*R.. 23

通常情况下,信号不会中断系统调用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

  • `poll`和`select`是特别的,因为它们永远不会重启.这由POSIX指定. (2认同)

bdo*_*lan 6

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