A Preliminary Study of PHP Multiprocesses-Orphans and Zombies

[Original Address:https://blog.ti-node.com/blog …]

In fact, you must remember that PHP’s multi-process is a highly valuable productivity tool that is well worth applying to production environments.

However, I think there are still two basic concepts to be said before we start bragging officially: orphan process and zombie process.

In the previous article, I talked about pcntl_fork () throughout my whole embarrassed conversation. Although fork production, regardless of postpartum care, actually it does not conform to mainstream values. Moreover, the operating system itself has limited resources, so unlimited production, regardless of care, will be too much for the operating system.

Orphan process means that the parent process finishes first after fork leaves the child process. This problem is very embarrassing because the subprocess has become helpless, homeless and orphaned. In other words, the parent process quits before the end of the child processes. These child processes will be adopted by the init (process ID 1) process and the collection of various data states will be completed. The init process is a strange process under the Linux system. this process is a process that runs with normal user rights but has super rights. in short, this process does initialization work when the Linux system starts, such as running getty, initializing the system according to the running level set in /etc/inittab, etc. of course, it also has the function of adopting orphans as mentioned above.

Zombie process means that the parent process leaves the child process at fork, and then the parent process does not call wait or waitpID after the child process is finished, thus causing the child process id, file descriptor, etc. to remain in the system, greatly wasting system resources. Therefore, the zombie process is harmful to the system, while the orphan process is relatively less serious. In Linux system, we can view the process through ps -aux. If there is a [Z+] mark, it is a zombie process.

In PHP, the parent process collects the state of the child processes through pcntl_wait () and pcntl_waitpid (). Still, it is necessary to demonstrate through code that:

Demonstrate and explain the emergence of the orphan process, and demonstrate the adoption of the orphan process by the init process:

<?php
        $pid = pcntl_fork();
        if( $pid > 0 ){
            // 显示父进程的进程ID,这个函数可以是getmypid(),也可以用posix_getpid()
            echo "Father PID:".getmypid().PHP_EOL;
            // 让父进程停止两秒钟,在这两秒内,子进程的父进程ID还是这个父进程
            sleep( 2 );
        } else if( 0 == $pid ) {
            // 让子进程循环10次,每次睡眠1s,然后每秒钟获取一次子进程的父进程进程ID
            for( $i = 1; $i <= 10; $i++ ){
                sleep( 1 );
                // posix_getppid()函数的作用就是获取当前进程的父进程进程ID
                echo posix_getppid().PHP_EOL;
            }
        } else {
            echo "fork error.".PHP_EOL;
        }

The operation results are as follows:

It can be seen that in the first two seconds, the parent process ID of the child process is 4129, but starting from the third second, since the parent process has already exited ahead of time and the child process has become an orphan process, the init process has adopted the child process, so the parent process ID of the child process has become 1.

Demonstrate and explain the emergence of the zombie process, and demonstrate the hazards of the zombie process:

<?php
        $pid = pcntl_fork();
        if( $pid > 0 ){
            // 下面这个函数可以更改php进程的名称
            cli_set_process_title('php father process');
            // 让主进程休息60秒钟
            sleep(60);
        } else if( 0 == $pid ) {
            cli_set_process_title('php child process');
            // 让子进程休息10秒钟,但是进程结束后,父进程不对子进程做任何处理工作,这样这个子进程就会变成僵尸进程
            sleep(10);
        } else {
            exit('fork error.'.PHP_EOL);
        }

The operation results are as follows:

By executing the ps -aux command, it can be seen that when the program runs in the first ten seconds, the state of php child process is listed as [S+]. However, after ten seconds, this state becomes [Z+], which is a zombie process that endangers the system.

So, is there a problem? How to avoid the zombie process? PHP helps us solve this problem through pcntl_wait () and pcntl_waitpid (). Those who know about Linux system programming should know by name that PHP is actually wrapping wait () and waitpid () in C language.

Through the code demonstration pcntl_wait () to avoid zombie processes, before starting, the relevant contents of pcntl_wait () should be popularized briefly: the function is to “wait or return the state of the child process”. When the parent process executes the function, it will block the state of waiting for the child process until the child process has exited or terminated for some reason. In other words, if the child process is not finished, then the parent process will wait all the time. If the child process is finished, then the parent process will immediately get the status of the child process. This function returns the process ID of the exiting subprocess or -1 if it fails.

我们将第二个案例中代码修改一下:
<?php
        $pid = pcntl_fork();
        if( $pid > 0 ){
            // 下面这个函数可以更改php进程的名称
            cli_set_process_title('php father process');
            
            // 返回$wait_result,就是子进程的进程号,如果子进程已经是僵尸进程则为0
            // 子进程状态则保存在了$status参数中,可以通过pcntl_wexitstatus()等一系列函数来查看$status的状态信息是什么
            $wait_result = pcntl_wait( $status );
            print_r( $wait_result );
            print_r( $status );
            
            // 让主进程休息60秒钟
            sleep(60);
        } else if( 0 == $pid ) {
            cli_set_process_title('php child process');
            // 让子进程休息10秒钟,但是进程结束后,父进程不对子进程做任何处理工作,这样这个子进程就会变成僵尸进程
            sleep(10);
        } else {
            exit('fork error.'.PHP_EOL);
        }

Save the file as wait.php, then PHP wait.php, through ps -aux in another terminal, can see in the first ten seconds, php child process is in the [S+] state, and then ten seconds later the process disappears, that is, it is recycled by the parent process and does not become a zombie process.

However, pcntl_wait () has a big problem, which is blocking. The parent process can only suspend and wait for the end or termination of the child process. During this period, the parent process can do nothing. This does not conform to the principle of “how fast, how good, and how economical”. Therefore, pcntl_waitpid () is on the scene. If the third parameter of pcntl_waitid ($ PID,&$ status, $ option = 0) is set to WNOHANG, then the parent process will not block until a child process exits or terminates, otherwise it will behave similarly to pcntl _ wait ().

Modify the code of the third case, but we do not add WNOHANG to demonstrate the pcntl_waitpid () function:

<?php
        $pid = pcntl_fork();
        if( $pid > 0 ){
            // 下面这个函数可以更改php进程的名称
            cli_set_process_title('php father process');
            
            // 返回值保存在$wait_result中
            // $pid参数表示 子进程的进程ID
            // 子进程状态则保存在了参数$status中
            // 将第三个option参数设置为常量WNOHANG,则可以避免主进程阻塞挂起,此处父进程将立即返回继续往下执行剩下的代码
            $wait_result = pcntl_waitpid( $pid, $status );
            var_dump( $wait_result );
            var_dump( $status );
            
            // 让主进程休息60秒钟
            sleep(60);
            
        } else if( 0 == $pid ) {
            cli_set_process_title('php child process');
            // 让子进程休息10秒钟,但是进程结束后,父进程不对子进程做任何处理工作,这样这个子进程就会变成僵尸进程
            sleep(10);
        } else {
            exit('fork error.'.PHP_EOL);
        }

The following is the result of the operation, one is the terminal window for executing php program, the other is the ps -aux terminal window. In fact, you can see that the main process is blocked until the tenth second when the child process exits and the parent process is no longer blocked:

Then we modify the fourth code and add the third parameter WNOHANG. the code is as follows:

<?php
        $pid = pcntl_fork();
        if( $pid > 0 ){
            // 下面这个函数可以更改php进程的名称
            cli_set_process_title('php father process');
            
            // 返回值保存在$wait_result中
            // $pid参数表示 子进程的进程ID
            // 子进程状态则保存在了参数$status中
            // 将第三个option参数设置为常量WNOHANG,则可以避免主进程阻塞挂起,此处父进程将立即返回继续往下执行剩下的代码
            $wait_result = pcntl_waitpid( $pid, $status, WNOHANG );
            var_dump( $wait_result );
            var_dump( $status );
            echo "不阻塞,运行到这里".PHP_EOL;
            
            // 让主进程休息60秒钟
            sleep(60);
            
        } else if( 0 == $pid ) {
            cli_set_process_title('php child process');
            // 让子进程休息10秒钟,但是进程结束后,父进程不对子进程做任何处理工作,这样这个子进程就会变成僵尸进程
            sleep(10);
        } else {
            exit('fork error.'.PHP_EOL);
        }

Face is the result of running, one is the terminal window for executing php program, the other is the ps -aux terminal window. In fact, you can see that the main process is blocked until the tenth second when the child process exits and the parent process is no longer blocked:

The problem has arisen. How did the php child process process state change to [Z+]? Look back and analyze the code:

We saw that the child process slept for ten seconds, and the parent process did not sleep before executing pcntl_waitpid () and was no longer blocked. Therefore, the main process executed itself first, and the child process ended after ten seconds. Naturally, the process state could not be recovered. If we modify the code, we will sleep for 15 seconds before the pcntl_waitpid () of the main process, so that the subprocess can be recycled. However, even if such modifications are made, there will still be a problem if you think carefully. That is, after the child process is finished, there will be a time difference of five seconds before the parent process performs pcntl_waitpid () recovery. Within this time difference, php child process will also be a zombie process. So, how to use pcntl_waitpid () correctly? After all, this is not very scientific.

So, it’s time to introduce signaling!

[Original Address:https://blog.ti-node.com/blog …]