想象一下,我有一个启动多个子进程的进程.父母需要知道孩子什么时候退出.
我可以使用waitpid
,但是如果/当父需要退出时,我无法告诉阻塞的线程waitpid
优雅地退出并加入它.让事情自己清理是件好事,但这可能不是什么大不了的事.
我可以用waitpid
用WNOHANG
,然后睡了一段任意时间,以防止忙等待.然而,我只能知道一个孩子是否经常退出.在我的情况下,我知道孩子何时立即离开可能不是超级关键,但我想尽快知道...
我可以使用信号处理程序SIGCHLD
,并在信号处理程序中执行当子项退出时要执行的任何操作,或者将消息发送到其他线程以执行某些操作.但是使用信号处理程序会稍微混淆代码流.
我真正想做的是使用waitpid
一些超时,比如5秒.由于退出进程不是一个时间关键的操作,我可以懒惰地发出线程信号退出,同时仍然在waitpid
其余时间阻塞它,随时准备做出反应.在linux中有这样的调用吗?在替代方案中,哪一个最好?
编辑:
基于回复的另一种方法是SIGCHLD
用pthread
\ 来阻塞所有线程_sigmask()
.然后在一个线程中,sigtimedwait()
一边寻找一边打电话SIGCHLD
.这意味着我可以超时调用并检查线程是否应该退出,如果没有,则保持阻塞状态等待信号.一旦a SIGCHLD
被传递到这个线程,我们可以立即对它做出反应,并且在等待线程的行中,不使用信号处理程序.
不要混合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()
如何控制超时,甚至可以与其他select()
基于IO的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);
出乎意料的简单:)
如果您确定程序中没有其他模块可以自行生成子进程,您甚至可以省略中级子进程.
这是个有趣的问题.我发现sigtimedwait可以做到.
编辑2016/08/29:感谢Mark Edington的建议.我在Ubuntu 16.04上测试了你的例子,它按预期工作.
注意:这仅适用于子进程.遗憾的是,在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#include #include #include #include #include #include #include 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; }
该功能可以通过信号中断,因此您可以在调用waitpid()之前设置定时器,并在定时器信号被提升时以EINTR退出.编辑:它应该像调用waitpid()之前调用alarm(5)一样简单.