异步清理子进程 [英] Cleaning up children processes asynchronously

查看:48
本文介绍了异步清理子进程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是高级 Linux 编程>,第 3.4.4 章中的一个示例.程序 fork() 和 exec() 是一个子进程.我希望父进程异步清理子进程(否则子进程将成为僵尸进程),而不是等待进程终止.可以使用信号 SIGCHLD 来完成.通过设置signal_handler,我们可以在子进程结束时完成清理工作.代码如下:

This is an example from <Advanced Linux Programming>, chapter 3.4.4. The programs fork() and exec() a child process. Instead of waiting for the termination of the process, I want the parent process to clean up the children process (otherwise the children process will become a zombie process) asynchronously. The can be done using the signal SIGCHLD. By setting up the signal_handler we can make the clean-up work done when the child process ends. And the code the following:

#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 execvp\n");
        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 (因为它没有打印出清理子进程由 SIGCHLD 处理.").这段代码有什么问题?

However, When I run the program in the terminal, the parent process never ends. And it seems that it doesn't execute the function clean_up_child_process (since it doesn't print out "Cleaning child process is taken care of by SIGCHLD."). What's the problem with this snippet of code?

推荐答案

针对 GNU/Linux 用户

我已经看过这本书了.虽然这本书把这个机制说成是:

for GNU/Linux users

I already read this book. Although the book talked about this mechanism as a:

引自本书 3.4.4 page 59:

quote from 3.4.4 page 59 of the book:

更优雅的解决方案是在子进程终止时通知父进程.

A more elegant solution is to notify the parent process when a child terminates.

但它只是说您可以使用 sigaction 来处理这种情况.

but it just said that you can use sigaction to handle this situation.

以下是如何以这种方式处理进程的完整示例.

Here is a complete example of how to handle processes in this way.

首先,我们为什么要使用这种机制?好吧,因为我们不想将所有进程同步在一起.

First why do ever we use this mechanism? Well, since we do not want to synchronize all processes together.

真实例子
假设您有 10 个 .mp4 文件,并且您想将它们转换为 .mp3 文件.好吧,我初级用户这样做:

real example
Imagine that you have 10 .mp4 files and you want to convert them to .mp3 files. Well, I junior user does this:

ffmpeg -i 01.mp4 01.mp3 

并重复此命令 10 次.高一点的用户会这样做:

and repeats this command 10 times. A little higher users does this:

ls *.mp4 | xargs -I xxx ffmpeg -i xxx xxx.mp3

这一次,这个命令管道每行10个mp4文件,每个一对一xargscode>,然后将它们一一转换为mp3.

This time, this command pipes all 10 mp4 files per line, each one-by-one to xargs and then they one by one is converted to mp3.

但我高级用户这样做:

ls *.mp4 | xargs -I xxx -P 0 ffmpeg -i xxx xxx.mp3

这意味着如果我有 10 个文件,创建 10 个进程并同时运行它们.并且有不同.在前两个命令中,我们只有 1 个进程;它被创建然后终止,然后继续到另一个.但是在 -P 0 选项的帮助下,我们同时创建了 10 个进程,实际上 10 个 ffmpeg 命令正在运行.

and this means if I have 10 files, create 10 processes and run them simultaneously. And there is BIG different. In the two previous command we had only 1 process; it was created then terminated and then continued to another one. But with the help of -P 0 option, we create 10 processes at the same time and in fact 10 ffmpeg commands are running.

现在异步清理孩子的目的变得更清晰了.事实上,我们想要运行一些新进程,但这些进程的顺序以及它们的退出状态对我们来说并不重要.这样我们就可以尽可能快地运行它们并减少时间.

Now the purpose of cleaning up children asynchronously becomes cleaner. In fact we want to run some new processes but the order of those process and maybe the exit status of them is not matter for us. In this way we can run them as fast as possible and reduce the time.

首先,您可以查看 man sigaction 以获取您想要的更多详细信息.

First you can see man sigaction for any more details you want.

第二次看到这个信号号:

Second seeing this signal number by:

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;
}

这是示例代码的作用:

  1. 创建 5 个子进程
  2. 然后进入内部while循环并暂停以接收信号.见man pause
  3. 然后当子进程终止时,父进程唤醒并调用signal_handler函数
  4. 继续到最后一个:sleep 9

输出:(17 表示 SIGCHLD)

output: (17 means 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

As you can see a.out process has 5 children. And They are running simultaneously. Then whenever each of them terminates, kernel sends the signal SIGCHLD to their parent that is: a.out

注意

如果我们不使用pause或任何机制让父进程可以wait等待它的子进程,那么我们将放弃创建的进程并且 upstart(= 在 Ubuntuinit 上)成为它们的父级.去掉 pause()

If we do not use pause or any mechanism so that the parent can wait for its children, then we will abandon the created processes and the upstart (= on Ubuntu or init) becomes parent of them. You can try it if you remove pause()

这篇关于异步清理子进程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆