代码之家  ›  专栏  ›  技术社区  ›  Greg Rogers

带超时的Waitpid等价物?

  •  45
  • Greg Rogers  · 技术社区  · 17 年前

    假设我有一个进程,它启动了几个子进程。父级需要知道子级何时退出。

    waitpid ,但如果/当父线程需要退出时,我无法告诉被阻塞的线程 子进程 优雅地退出并加入它。让事情自行清理是件好事,但可能没什么大不了的。

    我可以用 子进程 WNOHANG ,然后任意睡眠一段时间,以防止繁忙的等待。然而,我只能知道孩子是否经常离开。在我的情况下,我知道孩子什么时候马上离开可能不是非常关键,但我想尽快知道。。。

    我可以使用一个信号处理器 SIGCHLD ,并在信号处理程序中执行我在子线程退出时要执行的任何操作,或向其他线程发送消息以执行某些操作。但使用信号处理程序会使代码流有点混乱。

    我真正想做的是使用 子进程 在某个超时时间,比如说5秒。由于退出进程不是一个时间关键的操作,我可以在线程仍被阻塞的情况下,懒洋洋地发出退出线程的信号 剩下的时间,随时准备做出反应。 linux中有这样的调用吗?在备选方案中,哪一个是最好的?


    另一种基于回复的方法是阻止 西格尔德 pthread \ _sigmask() . 然后在一个线程中,继续调用 sigtimedwait() . 这意味着我可以在调用时超时并检查线程是否应该退出,如果不应该退出,则保持阻塞状态等待信号。每年一次 如果发送到这个线程,我们可以立即对它做出反应,并且在等待线程的队列中,而不使用信号处理程序。

    8 回复  |  直到 12 年前
        1
  •  41
  •   geocar    11 年前

    不要混在一起 alarm() wait() . 这样会丢失错误信息。

    使用自我管道技巧。这会将任何信号转换为信号 select()

    int selfpipe[2];
    void selfpipe_sigh(int n)
    {
        int save_errno = errno;
        (void)write(selfpipe[1], "",1);
        errno = save_errno;
    }
    void selfpipe_setup(void)
    {
        static struct sigaction act;
        if (pipe(selfpipe) == -1) { abort(); }
    
        fcntl(selfpipe[0],F_SETFL,fcntl(selfpipe[0],F_GETFL)|O_NONBLOCK);
        fcntl(selfpipe[1],F_SETFL,fcntl(selfpipe[1],F_GETFL)|O_NONBLOCK);
        memset(&act, 0, sizeof(act));
        act.sa_handler = selfpipe_sigh;
        sigaction(SIGCHLD, &act, NULL);
    }
    

    然后,类似waitpid的函数如下所示:

    int selfpipe_waitpid(void)
    {
        static char dummy[4096];
        fd_set rfds;
        struct timeval tv;
        int died = 0, st;
    
        tv.tv_sec = 5;
        tv.tv_usec = 0;
        FD_ZERO(&rfds);
        FD_SET(selfpipe[0], &rfds);
        if (select(selfpipe[0]+1, &rfds, NULL, NULL, &tv) > 0) {
           while (read(selfpipe[0],dummy,sizeof(dummy)) > 0);
           while (waitpid(-1, &st, WNOHANG) != -1) died++;
        }
        return died;
    }
    

    你可以从中看到 selfpipe_waitpid() 如何控制超时,甚至与其他 选择()

        2
  •  32
  •   mpartel    14 年前

    Fork是一个中间子级,它派生真实的子级和一个超时进程,并等待其所有(两个)子级。当一个退出时,它会杀死另一个并退出。

    pid_t intermediate_pid = fork();
    if (intermediate_pid == 0) {
        pid_t worker_pid = fork();
        if (worker_pid == 0) {
            do_work();
            _exit(0);
        }
    
        pid_t timeout_pid = fork();
        if (timeout_pid == 0) {
            sleep(timeout_time);
            _exit(0);
        }
    
        pid_t exited_pid = wait(NULL);
        if (exited_pid == worker_pid) {
            kill(timeout_pid, SIGKILL);
        } else {
            kill(worker_pid, SIGKILL); // Or something less violent if you prefer
        }
        wait(NULL); // Collect the other process
        _exit(0); // Or some more informative status
    }
    waitpid(intermediate_pid, 0, 0);
    

    出人意料的简单:)

    如果您确定程序中没有其他模块正在监视自己的子进程,甚至可以省略中间子进程。

        3
  •  17
  •   osexp2000    9 年前

    我发现 sigtimedwait 我能做到。

    谢谢马克·爱丁顿的建议。我已经在Ubuntu16.04上测试了你的例子,它可以正常工作。

    好的,Mark Edington的示例代码是 here :

    /* The program creates a child process and waits for it to finish. If a timeout
     * elapses the child is killed. Waiting is done using sigtimedwait(). Race
     * condition is avoided by blocking the SIGCHLD signal before fork().
     */
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <signal.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    
    static pid_t fork_child (void)
    {
        int p = fork ();
    
        if (p == -1) {
            perror ("fork");
            exit (1);
        }
    
        if (p == 0) {
            puts ("child: sleeping...");
            sleep (10);
            puts ("child: exiting");
            exit (0);
        }
    
        return p;
    }
    
    int main (int argc, char *argv[])
    {
        sigset_t mask;
        sigset_t orig_mask;
        struct timespec timeout;
        pid_t pid;
    
        sigemptyset (&mask);
        sigaddset (&mask, SIGCHLD);
    
        if (sigprocmask(SIG_BLOCK, &mask, &orig_mask) < 0) {
            perror ("sigprocmask");
            return 1;
        }
    
        pid = fork_child ();
    
        timeout.tv_sec = 5;
        timeout.tv_nsec = 0;
    
        do {
            if (sigtimedwait(&mask, NULL, &timeout) < 0) {
                if (errno == EINTR) {
                    /* Interrupted by a signal other than SIGCHLD. */
                    continue;
                }
                else if (errno == EAGAIN) {
                    printf ("Timeout, killing child\n");
                    kill (pid, SIGKILL);
                }
                else {
                    perror ("sigtimedwait");
                    return 1;
                }
            }
    
            break;
        } while (1);
    
        if (waitpid(pid, NULL, 0) < 0) {
            perror ("waitpid");
            return 1;
        }
    
        return 0;
    }
    
        4
  •  5
  •   Steve Baker    17 年前

        5
  •  3
  •   ony    14 年前

    select 会回来的 EINTR SIGCHLD 由孩子的身体发出信号。 我相信这应该行得通:

    while(1)
    {
      int retval = select(0, NULL, NULL, NULL, &tv, &mask);
      if (retval == -1 && errno == EINTR) // some signal
      { 
          pid_t pid = (waitpid(-1, &st, WNOHANG) == 0);
          if (pid != 0) // some child signaled
      }
      else if (retval == 0)
      {
          // timeout
          break;
      }
      else // error
    }
    

    pselect 超越电流 sigmask 避免不必要的信号干扰。

        6
  •  2
  •   Chris Dodd    17 年前

        7
  •  2
  •   Aktau    12 年前

    由于环境的原因,我绝对需要在主线程中运行这个函数,而使用self-pipe技巧或eventfd并不是很简单,因为我的epoll循环在另一个线程中运行。所以我找到了其他堆栈溢出处理程序。请注意,一般来说,用其他方式这样做要安全得多,但这很简单。如果有人想评论它到底有多糟糕,那我洗耳恭听。

    笔记 :绝对有必要阻止任何线程中的信号处理,但要在其中运行此线程的线程除外。我默认这样做,因为我认为在随机线程中处理信号很麻烦。

    static void ctlWaitPidTimeout(pid_t child, useconds_t usec, int *timedOut) {
        int rc = -1;
    
        static pthread_mutex_t alarmMutex = PTHREAD_MUTEX_INITIALIZER;
    
        TRACE("ctlWaitPidTimeout: waiting on %lu\n", (unsigned long) child);
    
        /**
         * paranoid, in case this was called twice in a row by different
         * threads, which could quickly turn very messy.
         */
        pthread_mutex_lock(&alarmMutex);
    
        /* set the alarm handler */
        struct sigaction alarmSigaction;
        struct sigaction oldSigaction;
    
        sigemptyset(&alarmSigaction.sa_mask);
        alarmSigaction.sa_flags   = 0;
        alarmSigaction.sa_handler = ctlAlarmSignalHandler;
        sigaction(SIGALRM, &alarmSigaction, &oldSigaction);
    
        /* set alarm, because no alarm is fired when the first argument is 0, 1 is used instead */
        ualarm((usec == 0) ? 1 : usec, 0);
    
        /* wait for the child we just killed */
        rc = waitpid(child, NULL, 0);
    
        /* if errno == EINTR, the alarm went off, set timedOut to true */
        *timedOut = (rc == -1 && errno == EINTR);
    
        /* in case we did not time out, unset the current alarm so it doesn't bother us later */
        ualarm(0, 0);
    
        /* restore old signal action */
        sigaction(SIGALRM, &oldSigaction, NULL);
    
        pthread_mutex_unlock(&alarmMutex);
    
        TRACE("ctlWaitPidTimeout: timeout wait done, rc = %d, error = '%s'\n", rc, (rc == -1) ? strerror(errno) : "none");
    }
    
    static void ctlAlarmSignalHandler(int s) {
        TRACE("ctlAlarmSignalHandler: alarm occured, %d\n", s);
    }
    

    编辑

        8
  •  2
  •   Pip    6 年前

    请检查以下代码片段以了解详细信息

    
    static bool waitpid_with_timeout(pid_t pid, int timeout_ms, int* status) {
        sigset_t child_mask, old_mask;
        sigemptyset(&child_mask);
        sigaddset(&child_mask, SIGCHLD);
    
        if (sigprocmask(SIG_BLOCK, &child_mask, &old_mask) == -1) {
            printf("*** sigprocmask failed: %s\n", strerror(errno));
            return false;
        }
    
        timespec ts;
        ts.tv_sec = MSEC_TO_SEC(timeout_ms);
        ts.tv_nsec = (timeout_ms % 1000) * 1000000;
        int ret = TEMP_FAILURE_RETRY(sigtimedwait(&child_mask, NULL, &ts));
        int saved_errno = errno;
    
        // Set the signals back the way they were.
        if (sigprocmask(SIG_SETMASK, &old_mask, NULL) == -1) {
            printf("*** sigprocmask failed: %s\n", strerror(errno));
            if (ret == 0) {
                return false;
            }
        }
        if (ret == -1) {
            errno = saved_errno;
            if (errno == EAGAIN) {
                errno = ETIMEDOUT;
            } else {
                printf("*** sigtimedwait failed: %s\n", strerror(errno));
            }
            return false;
        }
    
        pid_t child_pid = waitpid(pid, status, WNOHANG);
        if (child_pid != pid) {
            if (child_pid != -1) {
                printf("*** Waiting for pid %d, got pid %d instead\n", pid, child_pid);
            } else {
                printf("*** waitpid failed: %s\n", strerror(errno));
            }
            return false;
        }
        return true;
    }
    

    参考: https://android.googlesource.com/platform/frameworks/native/+/master/cmds/dumpstate/DumpstateUtil.cpp#46

        9
  •  2
  •   chys    5 年前

    如果您的程序仅在当代Linux内核(5.3或更高版本)上运行,则首选的方法是使用 pidfd_open ( https://lwn.net/Articles/789023/ https://man7.org/linux/man-pages/man2/pidfd_open.2.html

    select , poll epoll 它与您等待其他类型的文件描述符的方式相同。

    int fd = pidfd_open(pid, 0);
    struct pollfd pfd = {fd, POLLIN, 0};
    poll(&pfd, 1, 1000) == 1;
    
        10
  •  1
  •   Krunch    17 年前

    我可以为SIGCHLD使用信号处理程序,在信号处理程序中,当子线程退出时,我将执行任何操作,或者向其他线程发送消息以执行某些操作。但使用信号处理程序会使代码流有点混乱。

    为了避免竞争条件,您应该避免做任何比在信号处理程序中更改易失性标志更复杂的事情。

    我认为对你来说最好的选择是向父母发出信号。waitpid()然后将errno设置为EINTR并返回。此时,您检查waitpid返回值和errno,注意您已收到一个信号并采取适当的措施。

        11
  •  0
  •   Arran Cudbard-Bell    4 年前

    libkqueue 项目模拟 kqueue (BSD事件系统)并提供基本的过程监控 EVFILT_PROC + NOTE_EXIT .

    使用 kqueue libkqueue epoll *fd 功能( signalfd eventfd , pidfd 等等……)。

    #include <stdio.h>
    #include <stdint.h>
    #include <sys/event.h> /* kqueue header */
    #include <sys/types.h> /* for pid_t */
    
    /* Link with -lkqueue */
    
    int waitpid_timeout(pid_t pid, struct timespec *timeout)
    {
        struct kevent changelist, eventlist;
        int kq, ret;
    
        /* Populate a changelist entry (an event we want to be notified of) */
        EV_SET(&changelist, pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL);
    
        kq = kqueue();
    
        /* Call kevent with a timeout */
        ret = kevent(kq, &changelist, 1, &eventlist, 1, timeout);
    
        /* Kevent returns 0 on timeout, the number of events that occurred, or -1 on error */
        switch (ret) {
        case -1:
            printf("Error %s\n", strerror(errno));
            break;
    
        case 0:
            printf("Timeout\n");
            break;
    
        case 1:
            printf("PID %u exited, status %u\n", (unsigned int)eventlist.ident, (unsigned int)eventlist.data);
            break;
        }
        close(kq);
    
        return ret;
    }
    

    Linux上的幕后操作 uses either pidfd waiter thread 倾听 SIGCHLD 并通知一个或多个 kqueue waitid

    EVFILT_程序 支持已包括在 自成立以来 libkqueue 自从 v2.5.0