代码之家  ›  专栏  ›  技术社区  ›  Grumbel

如何连接挂在阻塞IO上的线程?

  •  26
  • Grumbel  · 技术社区  · 16 年前

    我如何正确地解决这种情况?我应该发送pthread_kill(theard,SIGIO)还是pthread_kill(theard,SIGALRM)来中断该块?这两个信号中有一个是正确的吗?或者有没有其他方法来解决这种情况,让子线程退出阻塞读取?

    目前有点困惑,因为我的谷歌搜索没有找到解决方案。

    编辑:我使用了SIGIO和SIGALRM,当我不安装信号处理程序时,它们会中断阻塞IO,但会在控制台上发出消息(“I/O可能”),但当我安装信号处理程序时,为了避免该消息,它们不再中断阻塞IO,因此线程不会终止。所以我回到了第一步。

    13 回复  |  直到 16 年前
        1
  •  18
  •   Craig McQueen Dr. Watson    11 年前

    实现这一点的标准方法是使用 pthread_cancel ,线程已完成的位置 pthread_cleanup_push pop 为它正在使用的任何资源提供清理。

    try {} catch() 在调用时在调用堆栈上 pthread_取消 可能会破坏你的整个过程。

    唯一的解决办法是处理 SIGUSR1 ,设置停止标志, pthread_kill(SIGUSR1) ,则线程在I/O上被阻塞的任何位置,如果 EINTR 在重试I/O之前检查stop标志。实际上,这在Linux上并不总是成功的,不知道为什么。

    埃因特尔 . 反向工程他们的文件描述符来关闭它不会切断它,尽管他们可能在等待信号量或其他资源。在这种情况下,根本不可能编写工作代码period。是的,这完全是脑损伤。与设计C++异常的人交谈 pthread_取消

        2
  •  15
  •   bog    16 年前

    我也建议使用select或其他非基于信号的方式终止线程。我们拥有线程的原因之一是试图摆脱信号疯狂。也就是说。。。

    通常,我们使用pthread_kill()和SIGUSR1或SIGUSR2向线程发送信号。其他建议的信号——SIGTERM、SIGINT、SIGKILL——具有进程范围的语义,您可能对此不感兴趣。

    线程接收到信号通常会中断EINTR的读取,除非它确实处于前面回答中提到的不可中断状态。但我认为不是这样,否则你对SIGALRM和SIGIO的实验不会终止这个过程。

    你的阅读可能是某种循环吗?如果读取终止时返回-1,则中断该循环并退出线程。

    #include <stdlib.h>
    #include <stdio.h>
    #include <pthread.h>
    #include <signal.h>
    
    int global_gotsig = 0;
    
    void *gotsig(int sig, siginfo_t *info, void *ucontext) 
    {
            global_gotsig++;
            return NULL;
    }
    
    void *reader(void *arg)
    {
            char buf[32];
            int i;
            int hdlsig = (int)arg;
    
            struct sigaction sa;
            sa.sa_handler = NULL;
            sa.sa_sigaction = gotsig;
            sa.sa_flags = SA_SIGINFO;
            sigemptyset(&sa.sa_mask);
    
            if (sigaction(hdlsig, &sa, NULL) < 0) {
                    perror("sigaction");
                    return (void *)-1;
            }
            i = read(fileno(stdin), buf, 32);
            if (i < 0) {
                    perror("read");
            } else {
                    printf("Read %d bytes\n", i);
            }
            return (void *)i;
    }
    
    main(int argc, char **argv)
    {
            pthread_t tid1;
            void *ret;
            int i;
            int sig = SIGUSR1;
    
            if (argc == 2) sig = atoi(argv[1]);
            printf("Using sig %d\n", sig);
    
            if (pthread_create(&tid1, NULL, reader, (void *)sig)) {
                    perror("pthread_create");
                    exit(1);
            }
            sleep(5);
            printf("killing thread\n");
            pthread_kill(tid1, sig);
            i = pthread_join(tid1, &ret);
            if (i < 0)
                    perror("pthread_join");
            else
                    printf("thread returned %ld\n", (long)ret);
            printf("Got sig? %d\n", global_gotsig);
    
    }
    
        3
  •  9
  •   Craig McQueen Dr. Watson    11 年前

    你的 select() 可能会有一个超时,即使它很少,以便在特定条件下正常退出线程。我知道,投票很糟糕。。。

    另一种选择是为每个子线程都设置一个管道,并将其添加到线程监视的文件描述符列表中。当希望子级退出时,从父级向管道发送一个字节。以每线程一个管道为代价不进行轮询。

        4
  •  6
  •   Craig McQueen Dr. Watson    11 年前

    这取决于它如何等待IO。

    真的没有办法摆脱这种“D”状态。线程不会响应信号(您可以发送它们,但它们将排队)。

    如果它是一个普通的IO函数,如read()、write()或一个等待函数,如select()或poll(),信号将正常传递。

        5
  •  6
  •   Alexis Wilke    5 年前

    随着事物的发展和新技术的出现,这个老问题很可能会得到新的答案 在线程中处理信号。

    自Linux内核2.6.22以来,系统提供了一个名为 signalfd() 它可用于为给定的一组Unix信号打开文件描述符(完全终止进程的信号除外)

    // defined a set of signals
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGUSR1);
    // ... you can add more than one ...
    
    // prevent the default signal behavior (very important)
    sigprocmask(SIG_BLOCK, &set, nullptr);
    
    // open a file descriptor using that set of Unix signals
    f_socket = signalfd(-1, &set, SFD_NONBLOCK | SFD_CLOEXEC);
    

    现在您可以使用 poll() select()

    如果您想要一个可以反复检查信号和其他文件描述符的循环,则非块非常重要(即,它对其他文件描述符也很重要)。

    我有这样一个实现,它可以与(1)计时器、(2)套接字、(3)管道、(4)Unix信号、(5)常规文件一起工作。实际上,任何文件描述符加上定时器。

    https://github.com/m2osw/snapcpp/blob/master/snapwebsites/libsnapwebsites/src/snapwebsites/snap_communicator.cpp
    https://github.com/m2osw/snapcpp/blob/master/snapwebsites/libsnapwebsites/src/snapwebsites/snap_communicator.h

    您可能还对以下库感兴趣: libevent

        6
  •  3
  •   Andrew Edgecombe    16 年前

    其想法是从主循环创建一个文件(或者按照超时的建议,每个线程创建一个文件-这将使您能够更好地控制哪些线程被唤醒)。所有阻塞文件I/O的线程都将使用它们试图操作的文件以及主循环创建的文件(作为读取文件描述符集的成员)执行select()。这将使所有select()调用返回。

    处理来自主循环的“事件”的代码需要添加到每个线程中。

    如果主循环需要唤醒所有线程,它可以写入文件或关闭文件。


        7
  •  2
  •   Chris Young    16 年前

    我认为,正如你所说,唯一的办法是发出一个信号,然后捕捉并妥善处理它。备选方案可能是SIGTERM、SIGUSR1、SIGQUIT、SIGHUP、SIGINT等。

    您还可以在输入描述符上使用select(),以便只在输入描述符准备就绪时读取。您可以使用select(),超时一秒钟,然后检查该线程是否应该完成。

        8
  •  1
  •   David Holm    16 年前

    杀死 “与我在加入前运行的线程函数相关的函数,该函数确保线程在合理的时间内可加入。当线程使用阻塞IO时,我尝试利用系统来打破锁。例如,当使用套接字时,我将有kill调用 关闭(2) 这将导致网络堆栈干净地终止它。

    Linux的套接字实现是线程安全的。

        9
  •  1
  •   HUAGHAGUAH    16 年前

    我很惊讶没有人建议pthread\u取消。我最近编写了一个多线程I/O程序,并调用了cancel()和join(),之后效果非常好。

    我最初尝试了pthread_kill(),但最终用我测试的信号终止了整个程序。

        10
  •  1
  •   bdonlan    16 年前

    如果在第三方库中阻塞ETEN,那么您可能需要考虑使用pthRead SKID与一个信号(UsR1等)调用一个空函数(而不是SIGIGIN)的组合,并实际关闭/替换所讨论的文件描述符。通过使用dup2将fd替换为/dev/null或类似内容,您将使第三方库在重试读取时获得文件结束结果。

    请注意,通过先复制原始套接字,可以避免实际关闭套接字。

        11
  •  0
  •   shodanex    16 年前

    根据不同的手册页,信号和线程在Linux上是一个微妙的问题。 您是否使用LinuxThreads或NPTL(如果您在Linux上)?

    我不确定这一点,但我认为信号处理程序会影响整个过程,所以要么终止整个过程,要么一切继续。

        12
  •  0
  •   Nicholas Mancuso    16 年前

    我认为最干净的方法是让线程在循环中使用条件变量继续。

    主线程可以在将循环谓词更改为false时发出条件信号。

    比如:

    while (!_finished)
    {
        pthread_cond_wait(&cond);
        handleio();
    }
    cleanup();
    

        13
  •  0
  •   luke    16 年前
    struct pollfd pfd;
    pfd.fd = socket;
    pfd.events = POLLIN | POLLHUP | POLLERR;
    pthread_lock(&lock);
    while(thread_alive)
    {
        int ret = poll(&pfd, 1, 100);
        if(ret == 1)
        {
            //handle IO
        }
        else
        {
             pthread_cond_timedwait(&lock, &cond, 100);
         }
    }
    pthread_unlock(&lock);
    

    thread_alive是一个特定于线程的变量,可与终止线程的信号结合使用。