异步清理子进程
Cleaning up children processes asynchronously
这是<高级Linux编程>,第3.4.4章。程序fork()和exec()是一个子进程。我希望父进程异步清理子进程(否则子进程将成为僵尸进程),而不是等待进程终止。可以使用信号SIGCHLD来完成。通过设置signal_handler,我们可以在子进程结束时完成清理工作。代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>
int spawn(char *program, char **arg_list){
pid_t child_pid;
child_pid = fork();
if(child_pid == 0){ // it is the child process
execvp(program, arg_list);
fprintf(stderr, "A error occured in execvpn");
return 0;
}
else{
return child_pid;
}
}
int child_exit_status;
void clean_up_child_process (int signal_number){
int status;
wait(&status);
child_exit_status = status; // restore the exit status in a global variable
printf("Cleaning child process is taken care of by SIGCHLD.n");
};
int main()
{
/* Handle SIGCHLD by calling clean_up_process; */
struct sigaction sigchld_action;
memset(&sigchld_action, 0, sizeof(sigchld_action));
sigchld_action.sa_handler = &clean_up_child_process;
sigaction(SIGCHLD, &sigchld_action, NULL);
int child_status;
char *arg_list[] = { //deprecated conversion from string constant to char*
"ls",
"-la",
".",
NULL
};
spawn("ls", arg_list);
return 0;
}
然而,当我在终端中运行程序时,父进程永远不会结束。它似乎没有执行函数clean_up_child_process(因为它没有打印出"Cleaning child process are care by SIGCHLD")。这段代码有什么问题?
从fork()
返回子进程pid后,父进程立即从main()
返回,它永远没有机会等待子进程终止。
适用于GNU/Linux用户
我已经读过这本书了。尽管书中谈到了这种机制作为:
引用本书第59页第3.4.4节:
一个更优雅的解决方案是在子进程终止时通知父进程。
但它只是说你可以使用sigaction
来处理这种情况。
以下是如何以这种方式处理流程的完整示例。
首先,我们为什么要使用这种机制?好吧,因为我们不想把所有的进程同步在一起。
真实示例
假设您有10个.mp4
文件,并且希望将它们转换为.mp3
文件。好吧,我初级用户这样做:
ffmpeg -i 01.mp4 01.mp3
并重复该命令10次。稍高一点的用户会这样做:
ls *.mp4 | xargs -I xxx ffmpeg -i xxx xxx.mp3
这一次,此命令每行管道所有10个mp4
文件,每个一个到xargs
,然后它们一个接一个转换为mp3
。
但我资深用户这样做:
ls *.mp4 | xargs -I xxx -P 0 ffmpeg -i xxx xxx.mp3
这意味着,如果我有10个文件,请创建10个进程并同时运行它们。还有BIG不同。在前两个命令中,我们只有一个过程;它被创建,然后被终止,然后继续到另一个。但在-P 0
选项的帮助下,我们同时创建了10个进程,实际上有10个ffmpeg
命令正在运行。
现在,异步清理儿童的目的变得更清洁了。事实上,我们想运行一些新进程,但这些进程的顺序以及它们的退出状态对我们来说并不重要。通过这种方式,我们可以尽可能快地运行它们,并减少时间。
首先,您可以查看man sigaction
了解更多您想要的详细信息。
第二次看到这个信号号通过:
T ❱ kill -l | grep SIGCHLD
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
样本代码
目的:使用SIGCHLD
清理子进程
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <wait.h>
#include <unistd.h>
sig_atomic_t signal_counter;
void signal_handler( int signal_number )
{
++signal_counter;
int wait_status;
pid_t return_pid = wait( &wait_status );
if( return_pid == -1 )
{
perror( "wait()" );
}
if( WIFEXITED( wait_status ) )
{
printf ( "job [ %d ] | pid: %d | exit status: %dn",signal_counter, return_pid, WEXITSTATUS( wait_status ) );
}
else
{
printf( "exit abnormallyn" );
}
fprintf( stderr, "the signal %d was receivedn", signal_number );
}
int main()
{
// now instead of signal function we want to use sigaction
struct sigaction siac;
// zero it
memset( &siac, 0, sizeof( struct sigaction ) );
siac.sa_handler = signal_handler;
sigaction( SIGCHLD, &siac, NULL );
pid_t child_pid;
ssize_t read_bytes = 0;
size_t length = 0;
char* line = NULL;
char* sleep_argument[ 5 ] = { "3", "4", "5", "7", "9" };
int counter = 0;
while( counter <= 5 )
{
if( counter == 5 )
{
while( counter-- )
{
pause();
}
break;
}
child_pid = fork();
// on failure fork() returns -1
if( child_pid == -1 )
{
perror( "fork()" );
exit( 1 );
}
// for child process fork() returns 0
if( child_pid == 0 ){
execlp( "sleep", "sleep", sleep_argument[ counter ], NULL );
}
++counter;
}
fprintf( stderr, "signal counter %dn", signal_counter );
// the main return value
return 0;
}
这就是示例代码的作用:
- 创建5个子进程
- 然后进入内部while循环并暂停以接收信号。见
man pause
- 然后,当子进程终止时,父进程将唤醒并调用
signal_handler
函数 - 继续到最后一个:
sleep 9
输出:(17表示SIGCHLD
)
ALP ❱ ./a.out
job [ 1 ] | pid: 14864 | exit status: 0
the signal 17 was received
job [ 2 ] | pid: 14865 | exit status: 0
the signal 17 was received
job [ 3 ] | pid: 14866 | exit status: 0
the signal 17 was received
job [ 4 ] | pid: 14867 | exit status: 0
the signal 17 was received
job [ 5 ] | pid: 14868 | exit status: 0
the signal 17 was received
signal counter 5
当您运行此示例代码时,在另一个终端上尝试以下操作:
ALP ❱ ps -o time,pid,ppid,cmd --forest -g $(pgrep -x bash)
TIME PID PPID CMD
00:00:00 5204 2738 /bin/bash
00:00:00 2742 2738 /bin/bash
00:00:00 4696 2742 _ redshift
00:00:00 14863 2742 _ ./a.out
00:00:00 14864 14863 _ sleep 3
00:00:00 14865 14863 _ sleep 4
00:00:00 14866 14863 _ sleep 5
00:00:00 14867 14863 _ sleep 7
00:00:00 14868 14863 _ sleep 9
如您所见,a.out
进程有5个子进程。它们同时运行。然后每当它们中的每一个终止时,内核都会向它们的父级发送信号SIGCHLD
,即:a.out
注意
如果我们不使用pause
或任何机制,使父级可以为其子级使用wait
,那么我们将放弃创建的进程,新贵(=在Ubuntu
或init
上)将成为它们的父级。如果您删除了pause()
,您可以尝试一下
我使用的是Mac,所以我的答案可能不太相关,但仍然如此。我编译时没有任何选项,所以可执行文件的名称是a.out
。
我对控制台也有同样的体验(进程似乎没有终止),但我注意到这只是终端故障,因为实际上你只需按Enter键,命令行就会返回,而实际上从其他终端窗口执行的ps
没有显示a.out
,也没有显示它启动的ls
。
此外,如果我运行./a.out >/dev/null
,它会立即结束。
所以上面的重点是,所有的东西实际上都终止了,只是终端因为某种原因冻结了。
接下来,为什么它从不打印Cleaning child process is taken care of by SIGCHLD.
。仅仅因为父进程先于子进程终止。SIGCHLD
信号无法传递到已终止的进程,因此永远不会调用处理程序。
书中说,父进程继续做一些其他事情,如果它真的做了,那么一切都很好,例如,如果在spawn()
之后添加sleep(1)
。
- 终止 QProcess 不会终止子进程
- 什么时候最好在子进程中使用 CPU 或 I/O 密集型代码 [ C++ ]
- 子进程更新共享 mmap 内存,但父进程没有更改
- 使用 waitpid 时等待子进程终止
- 使用重定向标准处理子进程中的 kbhit
- 由 JOB 中的进程启动的子进程是否可以将 JOB 属性设置为脱离作业?
- 是否可以将子进程的 stdout 重定向到父进程中的另一个文件?
- kill() 总是返回 0(成功),即使在子进程已经结束之后?
- 父进程和子进程之间的 POSIX 信号量
- 检测到由于操作系统内存不足而导致子进程终止
- 使用system()创建独立的子进程
- 从stdin读取时子进程挂起(fork/dup2竞争条件)
- 在 Bash 脚本中处理来自子进程的信号
- Qt C++ - 如何成功将数据传递给子进程?
- C++ 窗口本地系统模拟在子进程中失败
- 将类型化数组写入子进程 stdin 无法正常工作
- 将 nodejs 脚本作为子进程执行(而不是从其他脚本执行)
- 使用信号检测子进程何时终止的最佳方法是什么?
- 等待等待失效的子进程
- 如何识别子进程的子进程