适用于 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
这一次,这个命令pipes每行所有10个mp4文件,每个一个接一个到xargs然后他们一个接一个被转换到mp3。
但我高级用户这样做:
ls *.mp4 | xargs -I xxx -P 0 ffmpeg -i xxx xxx.mp3
这意味着如果我有 10 个文件,创建 10 个进程 并同时运行它们。并且有BIG不同。在前两个命令中,我们只有 1 个进程;它被创建然后终止,然后继续到另一个。但是在-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: %d\n",signal_counter, return_pid, WEXITSTATUS( wait_status ) );
}
else
{
printf( "exit abnormally\n" );
}
fprintf( stderr, "the signal %d was received\n", 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 %d\n", signal_counter );
// the main return value
return 0;
}
这就是示例代码的作用:
- 创建 5 个子进程
- 然后进入内部循环并暂停以接收信号。见
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 或任何机制让parent 可以wait 为其孩子,那么我们将放弃创建的进程和upstart (= on Ubuntu or init) 成为他们的父母。去掉pause()可以试试看