进程的退出

PHP 进程退出的几种情况

  1. 运行到最后一行语句

  2. 运行时遇到 return

  3. 运行时遇到 exit() 函数的时候

  4. 程序异常的时候

  5. 进程接收到中断信号

正常结束、异常结束(跟信号有关),不管以何种方式退出,都有一个终止状态码。

僵尸进程

僵尸进程(zombie process):指子进程已结束,但是父进程还没有使用 waitpcntl_wait)/pcntl_waitpidwaitpid) 来回收。

进程结束时并不会真的退出,还会驻留在内存中,父进程需要通过 waitpcntl_wait」函数来获取进程的终止状态码,同时该函数会释放终止进程的内存空间。否则容易造成僵尸进程过多,占用大量内存空间。

wait 函数挂起当前进程的执行直到一个子进程退出或接收到一个信号要求中断当前进程或调用一个信号处理函数。如果一个子进程在调用此函数时已经退出(俗称僵尸进程),此函数立刻返回。子进程使用的所有系统资源将被释放。关于 wait 在您系统上工作的详细规范请查看您系统的 wait(2)手册。

——pcntl_wait

示例:

<?php

$pid = pcntl_fork();

if (0 === $pid) {
    fprintf(STDOUT, "我是子进程,pid = %d,运行完我就没事啦。\n", posix_getpid());
} else {
    fprintf(STDOUT, "我是父进程,pid = %d。\n", posix_getpid());
    sleep(1);
    while (1) {
        # code...
        ;
    }
}

运行结果:


php demo5.php

我是父进程,pid = 1282。
我是子进程,pid = 1283,运行完我就没事啦。

进程状态:

laradock@3a6c2da5a07b:/var/www$ ps exj
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
    807    1282    1282     807 pts/2       1282 R+    1000   0:15 php demo5.php LC_ALL=en_US.UTF-8 NVM_DIR=/home/laradock/.nvm LS_COLORS=no=00:fi=00:
   1282    1283    1282     807 pts/2       1282 Z+    1000   0:00 [php] <defunct> // 僵尸进程

进程产生的文件

一个进程运行时,会在 /proc/PID 目录产生文件。

laradock@3a6c2da5a07b:/proc/1282$ ll
total 0
dr-xr-xr-x   9 laradock laradock 0 Mar  4 09:00 ./
dr-xr-xr-x 447 root     root     0 Mar  4 07:56 ../
-r--r--r--   1 laradock laradock 0 Mar  4 09:00 arch_status
dr-xr-xr-x   2 laradock laradock 0 Mar  4 09:00 attr/
-rw-r--r--   1 laradock laradock 0 Mar  4 09:00 autogroup
-r--------   1 laradock laradock 0 Mar  4 09:00 auxv
-r--r--r--   1 laradock laradock 0 Mar  4 09:00 cgroup
--w-------   1 laradock laradock 0 Mar  4 09:00 clear_refs
-r--r--r--   1 laradock laradock 0 Mar  4 09:00 cmdline
-rw-r--r--   1 laradock laradock 0 Mar  4 09:00 comm
-rw-r--r--   1 laradock laradock 0 Mar  4 09:00 coredump_filter
-r--r--r--   1 laradock laradock 0 Mar  4 09:00 cpu_resctrl_groups
-r--r--r--   1 laradock laradock 0 Mar  4 09:00 cpuset
lrwxrwxrwx   1 laradock laradock 0 Mar  4 09:00 cwd -> /var/www/hugoBlog/content/posts/dev/liunx下php多进程编程/code/
-r--------   1 laradock laradock 0 Mar  4 09:00 environ
lrwxrwxrwx   1 laradock laradock 0 Mar  4 09:00 exe -> /usr/bin/php7.4*
dr-x------   2 laradock laradock 0 Mar  4 09:00 fd/
dr-x------   2 laradock laradock 0 Mar  4 09:00 fdinfo/
-rw-r--r--   1 laradock laradock 0 Mar  4 09:00 gid_map
-r--------   1 laradock laradock 0 Mar  4 09:00 io
-r--r--r--   1 laradock laradock 0 Mar  4 09:00 limits
-rw-r--r--   1 laradock laradock 0 Mar  4 09:00 loginuid
dr-x------   2 laradock laradock 0 Mar  4 09:00 map_files/
-r--r--r--   1 laradock laradock 0 Mar  4 09:00 maps
-rw-------   1 laradock laradock 0 Mar  4 09:00 mem
-r--r--r--   1 laradock laradock 0 Mar  4 09:00 mountinfo
-r--r--r--   1 laradock laradock 0 Mar  4 09:00 mounts
-r--------   1 laradock laradock 0 Mar  4 09:00 mountstats
dr-xr-xr-x  53 laradock laradock 0 Mar  4 09:00 net/
dr-x--x--x   2 laradock laradock 0 Mar  4 09:00 ns/
-r--r--r--   1 laradock laradock 0 Mar  4 09:00 numa_maps
-rw-r--r--   1 laradock laradock 0 Mar  4 09:00 oom_adj
-r--r--r--   1 laradock laradock 0 Mar  4 09:00 oom_score
-rw-r--r--   1 laradock laradock 0 Mar  4 09:00 oom_score_adj
-r--------   1 laradock laradock 0 Mar  4 09:00 pagemap
-r--------   1 laradock laradock 0 Mar  4 09:00 patch_state
-r--------   1 laradock laradock 0 Mar  4 09:00 personality
-rw-r--r--   1 laradock laradock 0 Mar  4 09:00 projid_map
lrwxrwxrwx   1 laradock laradock 0 Mar  4 09:00 root -> //
-rw-r--r--   1 laradock laradock 0 Mar  4 09:00 sched
-r--r--r--   1 laradock laradock 0 Mar  4 09:00 schedstat
-r--r--r--   1 laradock laradock 0 Mar  4 09:00 sessionid
-rw-r--r--   1 laradock laradock 0 Mar  4 09:00 setgroups
-r--r--r--   1 laradock laradock 0 Mar  4 09:00 smaps
-r--r--r--   1 laradock laradock 0 Mar  4 09:00 smaps_rollup
-r--------   1 laradock laradock 0 Mar  4 09:00 stack
-r--r--r--   1 laradock laradock 0 Mar  4 09:00 stat
-r--r--r--   1 laradock laradock 0 Mar  4 09:00 statm
-r--r--r--   1 laradock laradock 0 Mar  4 09:00 status
-r--------   1 laradock laradock 0 Mar  4 09:00 syscall
dr-xr-xr-x   3 laradock laradock 0 Mar  4 09:00 task/
-rw-r--r--   1 laradock laradock 0 Mar  4 09:00 timens_offsets
-r--r--r--   1 laradock laradock 0 Mar  4 09:00 timers
-rw-rw-rw-   1 laradock laradock 0 Mar  4 09:00 timerslack_ns
-rw-r--r--   1 laradock laradock 0 Mar  4 09:00 uid_map
-r--r--r--   1 laradock laradock 0 Mar  4 09:00 wchan

如果我们开发一个守护进程的web项目,开启了大量的子进程,并且没有回收,那么服务器的内存和存储空间可能会被挤满,所以必须回收。

进程的回收

调用 pcntl_wait 函数会产生的几种情况

  1. 如果没有子进程,可能会返回错误

  2. 如果子进程还没有结束,就会阻塞父进程

    示例:

    <?php
    
    $pid = pcntl_fork();
    
    if (0 === $pid) {
        fprintf(STDOUT, "我是子进程,pid = %d,运行完我就没事啦。\n", posix_getpid());
        while (1) {
            # code...
        }
        // exit(256); // 0 = 成功、-1 = 失败(返回255)、最大值 255、超过 255 返回 0
    } else {
        fprintf(STDOUT, "我是父进程,pid = %d。\n", posix_getpid());
    
        $exitPid = pcntl_wait($status);
        if ($exitPid > 0){
            fprintf(STDOUT,"pid=%d,子进程已经挂了,它的终止状态码:%d,并且已经完全释放了它所占用的资源...\n", $pid, pcntl_wexitstatus($status));
        }else{
            fprintf(STDOUT,"wait error...\n");
        }
        while(1){
            fprintf(STDOUT,"我在打印...\n");
            sleep(3);
        }
    }
    

    运行结果:

    laradock@3a6c2da5a07b:$ php demo5.php
    我是父进程,pid = 1652。
    我是子进程,pid = 1653,运行完我就没事啦
    ... // 子进程没结束,父进程被阻塞
    

    进程状态:

    laradock@3a6c2da5a07b:/proc$ ps -exj
        PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
           0     333      333     333 pts/1       1655 Ss    1000   0:00 bash PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/larado
           0     807      807     807 pts/2       1652 Ss    1000   0:00 bash PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/larado
         807     1652     1652    807 pts/2       1652 S+    1000   0:00 php demo5.php LC_ALL=en_US.UTF-8 NVM_DIR=/home/laradock/.nvm LS_COLORS=no=00:fi=00:
        1652     1653     1652    807 pts/2       1652 R+    1000   0:08 php demo5.php LC_ALL=en_US.UTF-8 NVM_DIR=/home/laradock/.nvm LS_COLORS=no=00:fi=00:
    
  3. 给函数传递第三个参数 option 可以让父进程不阻塞

示例:

<?php

$pid = pcntl_fork();

if (0 === $pid) {
    fprintf(STDOUT, "我是子进程,pid = %d,运行完我就没事啦。\n", posix_getpid());

    sleep(3);

    exit(10);
}

while(1){
    fprintf(STDOUT, "我是父进程,pid = %d。\n", posix_getpid());

    $exitPid = pcntl_wait($status, WNOHANG);

    if ($exitPid > 0){
        fprintf(STDOUT,"pid=%d,子进程已经挂了,它的终止状态码:%d,并且已经完全释放了它所占用的资源...\n", $pid, pcntl_wexitstatus($status));
        break;
    }else if (0 === $exitPid) {
        fprintf(STDOUT,"我在打印1...\n");
    } else {
        $no = posix_errno();
        fprintf(STDOUT,"wait error... %s\n", posix_strerror($no));
    }
    fprintf(STDOUT,"我在打印2...\n");
    sleep(1);
}

fprintf(STDOUT, "父进程,pid = %d,结束。\n", posix_getpid());

运行结果:

laradock@3a6c2da5a07b:$ php demo5.php
我是父进程,pid = 1952。
我在打印1...
我在打印2...
我是子进程,pid = 1953,运行完我就没事啦。
我是父进程,pid = 1952。
我在打印1...
我在打印2...
我是父进程,pid = 1952。
我在打印1...
我在打印2...
我是父进程,pid = 1952。
我在打印1...
我在打印2...
我是父进程,pid = 1952。
pid=1953,子进程已经挂了,它的终止状态码:10,并且已经完全释放了它所占用的资源...
父进程,pid = 1952,结束。

判断进程的退出方式

可以通过函数判断进程的退出方式,获取终止状态码和中断信号编号。

posix_killkill):是用来发送一个中断信号(给进程或者一个进程组)。

示例:

<?php

echo posix_getpid();

$pid = pcntl_fork();

if ($pid==0){

    while(1){
        fprintf(STDOUT,"pid=%d child process do...\n",posix_getpid());
        sleep(2);
        // return;
    }
}

while (1){

    $pid = pcntl_wait($status,WNOHANG);//让父进程以非阻塞方式运行

    fprintf(STDOUT,"exit pid=%d\n",$pid);

    if ($pid>0){

        // 正常退出
        if(pcntl_wifexited($status)){
            fprintf(STDOUT,"正常退出:exit pid=%d,exit-status=%d\n",$pid,pcntl_wexitstatus($status));

        }

        //中断退出
        else if (pcntl_wifsignaled($status)){

            fprintf(STDOUT,"中断退出1:exit pid=%d,SIGNUM=%d\n",$pid,pcntl_wtermsig($status));
        }

        // 一般是发送SIGSTOP SIGTSTP 要让进程停止
        // 以阻塞方式回收,才能生效 $pid = pcntl_wait($status,WNOHANG);
        else if (pcntl_wifstopped($status)){
            fprintf(STDOUT,"中断退出2:exit pid=%d,SIGNUM=%d\n",$pid,pcntl_wstopsig($status));
        }

    }

    fprintf(STDOUT,"PID=%d father process...\n",posix_getpid());

    sleep(3);
}

中断信号有自己的信号编号和对应的信号名字,信号编号是以非负数值来表示,信号名字是以SIG开头的,可以通过 kill -l 查看中断信号列表。