Waitpid相当于超时?
想象一下,我有一个启动多个subprocess的进程。 家长需要知道孩子什么时候退出。
我可以使用waitpid
,但是如果/当父节点需要退出,我无法告诉在waitpid
阻塞的线程正常退出并join它。 把事情清理干净真好,但这可能不是什么大事。
我可以在WNOHANG
使用waitpid
,然后在一段时间内进入睡眠状态,以防止忙碌的等待。 然而,我只能知道,如果一个孩子经常退出。 就我而言,我可能并不是超级批判,因为我知道孩子什么时候能够立即退出,但是我希望尽快知道…
我可以使用SIGCHLD
的信号处理程序,并在信号处理程序中执行小孩退出时的任何操作,或者发送消息到不同的线程来执行某些操作。 但是使用一个信号处理程序会混淆一点点代码的stream程。
我真正想要做的就是在暂停时使用waitpid
,比如5秒。 既然退出这个过程不是一个时间要求很高的操作,我可以懒惰地指示线程退出,而在其余的时间里仍然阻塞它,随时准备做出反应。 有没有这样的电话在Linux? 哪一个最好呢?
编辑:
另一种基于回复的方法是用pthread
\ _sigmask()
在所有线程中阻塞SIGCHLD
。 然后在一个线程中,一边查找SIGCHLD
一边继续调用sigtimedwait()
。 这意味着我可以在该呼叫中超时,并检查线程是否应该退出,如果没有,则保持阻止等待该信号。 一旦一个SIGCHLD
传递给这个线程,我们可以立即对它作出反应,并在等待线程的行中,而不使用信号处理程序。
该函数可以被一个信号中断,所以你可以在调用waitpid()之前设置一个定时器,当定时器信号上升时,它将以一个EINTR退出。 编辑:它应该像调用waitpid()之前调用alarm(5)一样简单。
不要把alarm()
和wait()
混合在一起。 您可以通过这种方式丢失错误信息。
使用自我pipe道技巧。 这将任何信号转换为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()
看到如何控制超时,甚至可以与其他基于select()
的IO混合使用。
把一个中间的孩子叉起来,这个孩子会分出真正的孩子,然后等待所有的孩子。 当一个退出,它会杀死另一个退出。
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);
令人惊讶的简单:)
如果您确定程序中没有其他模块正在使用自己的subprocess,您甚至可以忽略中间的subprocess。
这是个有趣的问题。 我发现sigtimedwait可以做到这一点。
编辑2016/08/29:感谢马克·艾丁顿的build议。 我在Ubuntu 16.04上testing了你的例子,它按预期工作。
注意:这只适用于subprocess。 可惜的是,在Linux / Unix中似乎没有Window的WaitForSingleObject(unrelated_process_handle,timeout)的等效方法来在超时内得到无关进程终止的通知。
好的,Mark Edington的示例代码在这里 :
/* 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; }
如果您打算使用信号(根据Steve的build议),您可以在想要退出时手动发送信号。 这将导致waitpid返回EINTR,然后线程可以退出。 无需定期报警/重启。
我想在SIGCHLD
发信号的时候select
会返回EINTR
。 我相信这应该工作:
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
并避免来自不需要的信号的中断。
由于情况下,我绝对需要这个在主线程中运行,使用自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); }
编辑 :我已经过渡到使用与我现有的epoll()基于eventloop,使用timerfd很好的集成的解决scheme。 自从我使用epoll之后,我并没有失去任何平台独立性,而且我获得了额外的睡眠,因为我知道multithreading和UNIX信号的邪恶组合不会再伤害我的程序。
我可以使用SIGCHLD的信号处理程序,并在信号处理程序中执行小孩退出时的任何操作,或者发送消息到不同的线程来执行某些操作。 但是使用一个信号处理程序会混淆一点点代码的stream程。
为了避免竞争条件,你应该避免做比在信号处理程序中更改易失性标志更复杂的任何事情。
我认为你的情况最好的select是向父母发送信号。 waitpid()然后将errno设置为EINTR并返回。 此时你检查waitpid返回值和errno,注意你已经发送了一个信号并采取适当的行动。